#18810: feat(hooks): wire before_tool_call/after_tool_call with veto support
docs
channel: mattermost
agents
size: L
Cluster:
Plugin Hook Enhancements
# PR: Wire before_tool_call and after_tool_call Hooks with Veto Support
## Summary
This PR implements real-time tool call interception and veto capability by wiring the existing `before_tool_call` and `after_tool_call` hooks into the actual tool execution pipeline.
## What Changed
### Hook Wiring
- **`src/agents/pi-tool-definition-adapter.ts`**: Added `hookContext` parameter to `toToolDefinitions()` to pass `agentId` and `sessionKey` to hooks. Fixed `runAfterToolCall` calls to include full context.
- **`src/agents/pi-tools.before-tool-call.ts`**: Added `after_tool_call` emission in `wrapToolWithBeforeToolCallHook` for all outcomes (success, error, blocked).
- **`src/plugins/types.ts`**: Added `blocked` and `blockReason` fields to `PluginHookAfterToolCallEvent`.
### Tests
- **`src/plugins/hooks.tool-call.test.ts`**: New test file with 9 test cases covering hook firing, veto mechanism, payload structure, and context propagation.
## Why It Matters
Before this change:
- `before_tool_call` and `after_tool_call` were defined but NOT wired into tool execution
- Plugins could not block tool execution in real-time
- `after_tool_call` received incomplete context (missing `agentId`, `sessionKey`)
After this change:
- Plugins can intercept tool calls BEFORE execution
- Returning `{ block: true, blockReason: "..." }` prevents tool execution
- `after_tool_call` includes blocked status and full context
- sessionKey is consistently available for session tracking
## Veto Contract
```typescript
// In before_tool_call handler:
api.on("before_tool_call", async (event, ctx) => {
if (shouldBlock(event.toolName, event.params)) {
return { block: true, blockReason: "Policy violation" };
}
return undefined; // allow
});
```
When `block: true` is returned:
1. Tool execution does NOT run
2. Error is thrown with `blockReason`
3. `after_tool_call` fires with `blocked: true`
## Event Payload Schema
### before_tool_call
```typescript
event: { toolName: string; params: Record<string, unknown> }
ctx: { agentId?: string; sessionKey?: string; toolName: string }
result: { params?: Record<string, unknown>; block?: boolean; blockReason?: string }
```
### after_tool_call
```typescript
event: {
toolName: string;
params: Record<string, unknown>;
result?: unknown;
error?: string;
durationMs?: number;
blocked?: boolean; // NEW
blockReason?: string; // NEW
}
ctx: { agentId?: string; sessionKey?: string; toolName: string }
```
## Backward Compatibility
- All new fields are optional
- Default behavior (no hooks registered) unchanged
- Existing plugins continue to work without modification
## Security Impact
- Plugins can now block dangerous tool calls in real-time
- Fail-closed: if hook throws, tool execution continues (resilient)
- Policy enforcement happens BEFORE side effects
## Testing
```bash
pnpm test src/plugins/hooks.tool-call.test.ts
```
9 test cases covering:
1. before_tool_call receives correct payload
2. Veto blocks tool execution
3. Blocked call includes blockReason
4. after_tool_call receives result/error/durationMs
5. sessionKey present in context
6. Parameter modification works
7. No-hooks bypass works
8. Hook error resilience
9. Error case handling
## Files Changed
| File | Changes |
|------|---------|
| `src/plugins/types.ts` | +2 lines (blocked, blockReason fields) |
| `src/agents/pi-tool-definition-adapter.ts` | ~50 lines (context wiring) |
| `src/agents/pi-tools.before-tool-call.ts` | ~30 lines (after_tool_call emission) |
| `src/plugins/hooks.tool-call.test.ts` | NEW (9 test cases) |
## Related
- docs/hooks_tool_calls.md - Hook documentation (created in this branch)
- tasks-hooks/ - Implementation task files
Most Similar PRs
#10678: feat(hooks): wire after_tool_call hook into tool execution pipeline
by yassinebkr · 2026-02-06
79.4%
#18860: feat(agents): expose tools and their schemas via new after_tools_re...
by lan17 · 2026-02-17
75.2%
#16028: feat/before-tool-result
by ambushalgorithm · 2026-02-14
75.1%
#22068: Add tool:before/tool:after internal hook events
by yhindy · 2026-02-20
74.2%
#20580: feat(hooks): bridge after_tool_call to internal hook handler system
by CryptoKrad · 2026-02-19
73.7%
#17667: feat: tool-hooks extension — run shell commands on tool calls
by FaradayHunt · 2026-02-16
72.4%
#19422: fix: pass session context to plugin tool hooks in toToolDefinitions
by namabile · 2026-02-17
70.9%
#14544: feat: add before_context_send plugin hook
by Windelly · 2026-02-12
70.7%
#22624: feat(plugins): add before_context_send hook and model routing via b...
by davidrudduck · 2026-02-21
70.6%
#11071: Plugins: add tool_result_received hook for output interception
by ThomasLWang · 2026-02-07
69.8%