#10678: feat(hooks): wire after_tool_call hook into tool execution pipeline
agents
stale
Cluster:
Plugin Hook Enhancements
# feat(plugins): add `after_tool_call` hook
## Summary
Adds the `after_tool_call` plugin hook — the complement to `before_tool_call` (merged in #5513). Together they form the complete tool lifecycle pair for plugin observability.
## What this PR does
- **New file: `pi-tools.after-tool-call.ts`** — exports `runAfterToolCallHook()` and `wrapToolWithAfterToolCallHook()`
- **New file: `pi-tools.after-tool-call.test.ts`** — 7 tests covering the full contract
- **Wired into `pi-tools.ts`** — every tool now gets both `before_tool_call` and `after_tool_call` hooks applied
## Design
Follows the exact same pattern as `before_tool_call` with key differences:
| | `before_tool_call` | `after_tool_call` |
|---|---|---|
| **Timing** | Before tool execution | After tool execution (in `finally`) |
| **Blocking** | Yes — can block/modify params | No — fire-and-forget |
| **Receives** | `toolName`, `params` | `toolName`, `params`, `result`, `error`, `durationMs` |
| **Hook errors** | Logged, tool proceeds | Logged, never crash tool execution |
| **Zero-overhead** | `hasHooks` fast-path | `hasHooks` fast-path |
### Fire-and-forget semantics
The hook promise is deliberately **not awaited** — `runAfterToolCallHook` returns `void` (not `Promise<void>`). This ensures:
- Tool results are returned immediately to the model
- Slow hooks don't degrade agent responsiveness
- Async hook errors are caught via `.catch()` on the promise
### Error resilience
Three layers of protection:
1. `hasHooks` check — zero overhead when no plugins register `after_tool_call`
2. Synchronous `try/catch` around `hookRunner.runAfterToolCall()` call
3. `.catch()` on the returned promise for async failures
## Tests
```
✓ executes tool normally when no hook is registered
✓ fires with correct event data after tool execution
✓ is fire-and-forget (does not block tool result)
✓ receives error info when tool execution fails
✓ continues execution when hook throws synchronously
✓ continues execution when hook rejects asynchronously
✓ normalizes non-object params for hook contract
```
## References
- Closes #7297
- Related: #5513 (`before_tool_call` implementation)
<!-- greptile_comment -->
<h2>Greptile Overview</h2>
<h3>Greptile Summary</h3>
This PR introduces an `after_tool_call` plugin hook and wires it into the tool wrapping pipeline so plugins can observe tool outcomes (result/error and duration) without blocking execution. It adds a new wrapper (`wrapToolWithAfterToolCallHook`) plus a vitest suite covering the hook contract, and updates `createOpenClawCodingTools` to apply both before/after tool-call hooks to every tool before abort-signal wrapping.
<h3>Confidence Score: 3/5</h3>
- This PR is close to mergeable but has a hook semantics issue that can mislead plugins.
- Core implementation and tests look consistent with existing hook runner patterns, but the current wrapper order will emit `after_tool_call` events even when `before_tool_call` blocks a tool call, which is likely not the intended meaning of “after tool call/execution” for observability hooks.
- src/agents/pi-tools.ts (hook wrapping order/semantics), src/agents/pi-tools.after-tool-call.ts (event semantics)
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#20580: feat(hooks): bridge after_tool_call to internal hook handler system
by CryptoKrad · 2026-02-19
85.1%
#22068: Add tool:before/tool:after internal hook events
by yhindy · 2026-02-20
83.4%
#16028: feat/before-tool-result
by ambushalgorithm · 2026-02-14
82.8%
#17667: feat: tool-hooks extension — run shell commands on tool calls
by FaradayHunt · 2026-02-16
81.6%
#11071: Plugins: add tool_result_received hook for output interception
by ThomasLWang · 2026-02-07
79.8%
#18810: feat(hooks): wire before_tool_call/after_tool_call with veto support
by vjranagit · 2026-02-17
79.4%
#7771: Hooks: wire lifecycle events and tests
by rabsef-bicrym · 2026-02-03
78.6%
#18889: feat(hooks): add agent and tool lifecycle boundaries
by vincentkoc · 2026-02-17
78.4%
#19422: fix: pass session context to plugin tool hooks in toToolDefinitions
by namabile · 2026-02-17
78.3%
#14544: feat: add before_context_send plugin hook
by Windelly · 2026-02-12
76.4%