fix(hooks): deduplicate after_tool_call hook in embedded runs [AI-assisted]#27283
Closed
jbeno wants to merge 1 commit intoopenclaw:mainfrom
Closed
fix(hooks): deduplicate after_tool_call hook in embedded runs [AI-assisted]#27283jbeno wants to merge 1 commit intoopenclaw:mainfrom
jbeno wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Contributor
Greptile SummaryRemoved duplicate Key Changes
ImpactPlugin authors observing Confidence Score: 5/5
Last reviewed commit: c129a1a |
Contributor
Author
|
@Patrick-Barletta and @lailoo can you take a look since you've spent some time working on hooks? |
18 tasks
Member
Member
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
after_tool_callplugin hook fires twice per tool execution in embedded agent runs. Two independent code paths both dispatch the hook:pi-tool-definition-adapter.ts(the tool adapter wrapper) andhandleToolExecutionEndinpi-embedded-subscribe.handlers.tools.ts(the subscription event handler).after_tool_callsee duplicate events for every tool call, forcing them to implement client-side deduplication workarounds (windowed hash maps, etc.). This is a correctness bug in the hook lifecycle.after_tool_calldispatch frompi-tool-definition-adapter.ts. The subscription handler (handleToolExecutionEnd) is now the single source of truth — it already has richer context (duration, sanitized result, session info). Added regression tests and an integration test that verifies the hook fires exactly once.before_tool_callbehavior is untouched (it was already deduplicated in PR fix(hooks): fire before_tool_call hook exactly once per tool invocation (#15502) #15635). The subscription handler dispatch logic is unchanged.consumeAdjustedParamsForToolCallcleanup remains in the adapter to prevent memory leaks frombefore_tool_callparameter tracking.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
before_tool_call— this PR applies the same fix toafter_tool_call)User-visible / Behavior Changes
Plugins listening on
after_tool_callwill now receive exactly one event per tool execution instead of two. Plugins with deduplication workarounds will continue to work but the workarounds become unnecessary.Security Impact (required)
NoNoNoNoNoRepro + Verification
Environment
after_tool_callSteps
after_tool_calleventsopenclaw agent)Expected
after_tool_callevent per tool executionActual (on
main)after_tool_callevents per tool execution — one from the adapter wrapper, one from the subscription handlerEvidence
New integration test (
pi-tool-definition-adapter.after-tool-call.fires-once.test.ts): simulates the full embedded runtime flow (adapter execute + subscription handler event) and assertstoHaveBeenCalledTimes(1). This test was verified to fail withtoHaveBeenCalledTimes(2)when the bug is reintroduced.Regression guard test (
pi-tool-definition-adapter.after-tool-call.test.ts): updated to assert the adapter does NOT callrunAfterToolCall, confirming the hook dispatch was removed from the adapter.Full test suite: 1417 test files, 11613 tests — all passing (
pnpm build && pnpm check && pnpm test).Human Verification (required)
mainwith a plugin observingafter_tool_call— confirmed 2 events fire per tool callconsumeAdjustedParamsForToolCallcleanup still runs in the adapter to prevent leaks frombefore_tool_callparameter trackingafter_tool_calldispatch paths differ there and are not affected by this changeCompatibility / Migration
YesNoNoFailure Recovery (if this breaks)
c129a1a74— it re-adds theafter_tool_calldispatch block topi-tool-definition-adapter.tssrc/agents/pi-tool-definition-adapter.tsafter_tool_callstops firing entirely (zero times), it means the subscription handler path is not being reached — but that would also breakbefore_tool_callwhich uses the same handler patternRisks and Mitigations
after_tool_callwith the raw (unsanitized) tool result, while the subscription handler dispatches with the sanitized result. Plugins that depended on seeing the raw result will now only see the sanitized version.AI Disclosure: This PR was built with AI assistance (Cursor + Claude).
main(bug present) and fix branch (bug resolved).before_tool_callhalf was already deduplicated in PR fix(hooks): fire before_tool_call hook exactly once per tool invocation (#15502) #15635; this PR completes the same fix forafter_tool_call.Made with Cursor