Skip to content

fix: remove duplicate BEFORE_TOOL/AFTER_TOOL hook firing in legacy async path - #3456

Open
praisonai-triage-agent[bot] wants to merge 1 commit into
mainfrom
claude/issue-3455-20260727-1716
Open

fix: remove duplicate BEFORE_TOOL/AFTER_TOOL hook firing in legacy async path#3456
praisonai-triage-agent[bot] wants to merge 1 commit into
mainfrom
claude/issue-3455-20260727-1716

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Fixes #3455

Summary

The legacy async tool loop in _achat_completion (chat_mixin.py) fired the BEFORE_TOOL and AFTER_TOOL hooks inline, then called execute_tool_async, which also fires the same two hooks (guarded). This double-fired hooks when registered and, when none were registered, built BeforeToolInput/AfterToolInput (two os.getcwd() syscalls + two str(time.time())) per tool call for no consumer.

Change

Removed the redundant inline BEFORE_TOOL/AFTER_TOOL blocks (chat_mixin.py:3752-3801) and rely on the single already-guarded dispatch inside execute_tool_async (execution_mixin.py:1254/:1279), which the loop already calls.

Capability preserved

  • BEFORE_TOOL/AFTER_TOOL fire once via execute_tool_async with identical inputs.
  • Tool blocking preserved (block sentinel returned and appended to results).
  • modified_input argument mutation and AFTER_TOOL additional_context aggregation preserved.
  • Non-fatal try/except isolation preserved (inside execute_tool_async).

Net: 1 file changed, 4 insertions(+), 47 deletions(-).

Validation

  • tests/unit/agent/test_achat_unified_dispatch.py β€” 5 passed
  • Hook + tool tests (test_hooks, test_before_tool_definitions, test_hook_bridge, etc.) β€” 29 passed

Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved consistency and safety when tools are executed during asynchronous chat interactions.
    • Centralized tool execution handling so applicable safeguards and validations are applied reliably.

…ync path (fixes #3455)

The legacy async tool loop in _achat_completion fired BEFORE_TOOL/AFTER_TOOL
inline and then again inside execute_tool_async, double-firing hooks when
registered and building hook inputs (os.getcwd/time.time) with no consumer
when none are. Rely on the single guarded dispatch in execute_tool_async,
which already handles blocking, arg mutation and after-context aggregation.

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more β†’

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account β†’

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us β†’

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor
βœ… Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:manual-review Blocked: requires manual review pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jul 27, 2026
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. πŸŽ‰

ℹ️ Recent review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8dec5be5-071f-4100-9286-327f5277b953

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 30c44e3 and a900a5c.

πŸ“’ Files selected for processing (1)
  • src/praisonai-agents/praisonaiagents/agent/chat_mixin.py

πŸ“ Walkthrough

Walkthrough

The legacy async tool-call path in ChatMixin._achat_completion now delegates tool execution directly to execute_tool_async, removing duplicate inline BEFORE_TOOL and AFTER_TOOL hook handling.

Changes

Async tool hook execution

Layer / File(s) Summary
Centralized async tool execution
src/praisonai-agents/praisonaiagents/agent/chat_mixin.py
_achat_completion removes inline tool hook dispatch and invokes execute_tool_async with the current tools override, leaving hook enforcement to the centralized execution pipeline.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: pipeline/awaiting-merge-gate

Suggested reviewers: cursoragent, mervinpraison

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately summarizes the main change: removing duplicate BEFORE_TOOL/AFTER_TOOL hook firing in the legacy async path.
Linked Issues check βœ… Passed The change matches #3455 by removing inline duplicate hook dispatch and relying on execute_tool_async for guarded hook execution.
Out of Scope Changes check βœ… Passed The summary shows a focused edit to chat_mixin.py with no unrelated behavior changes beyond the hook-dispatch refactor.
Docstring Coverage βœ… Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-3455-20260727-1716

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Removes duplicate inline tool-hook dispatch from the legacy asynchronous chat loop.

  • Delegates BEFORE_TOOL and AFTER_TOOL handling exclusively to execute_tool_async.
  • Preserves guarded hook execution, tool blocking, input mutation, and post-tool context aggregation.

Confidence Score: 5/5

The PR appears safe to merge with no actionable defects identified.

The legacy asynchronous loop now relies on the existing guarded tool executor, which preserves hook blocking, argument mutation, failure isolation, and after-hook context handling without firing hooks twice.

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/agent/chat_mixin.py Removes redundant hook dispatch while retaining the established asynchronous tool-execution pipeline.

Reviews (1): Last reviewed commit: "fix: remove duplicate BEFORE_TOOL/AFTER_..." | Re-trigger Greptile

@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK β€” never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params β€” only if absolutely required; we already support many params.
  6. Repo routing: agent-callable tools β†’ PraisonAI-Tools; lifecycle plugins β†’ PraisonAI-Plugins; optional sandbox backends β†’ PraisonAI-Plugins (praisonai.sandbox entry point) β€” request changes if wrongly added to praisonaiagents/

Phase 2: FIX Valid Issues
7. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
8. Also independently identify and fix any gaps or issues you find in the changed code β€” do not rely only on prior reviewer feedback
9. Push all code fixes directly to THIS branch (do NOT create a new PR)
10. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
11. If all issues are resolved, approve the PR / close the Issue
12. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jul 27, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task β€”β€” View job


Final Architecture Review β€” PR #3456 βœ… Approved

  • Read AGENTS.md guidelines
  • Review changed section in chat_mixin.py
  • Verify hook parity in execute_tool_async
  • Confirm blocking / modified_input / additional_context preserved
  • Run tests
  • Final verdict

Phase 1 β€” Architecture review (per AGENTS.md)

Criterion Verdict
Protocol-driven βœ… No new heavy impl; consolidates onto the existing guarded pipeline.
Backward compatible βœ… Hooks still fire once with identical inputs; zero feature regression.
Performance βœ… Improved hot path β€” removes 2 unconditional os.getcwd() syscalls + 2 str(time.time()) and needless BeforeToolInput/AfterToolInput construction per tool call when no hooks are registered. execute_tool_async is gated on has_hooks(...) so no work when unused.
SDK value βœ… Genuine simplification: -47/+4, removes duplicated dispatch, single source of truth. No scope creep, no new params/exports, Agent class untouched.
Repo routing βœ… Bug fix in praisonaiagents/ core β€” correct location.

Phase 2 β€” Capability parity verification

I traced the sole remaining dispatch (execution_mixin.py:1251-1300) against the deleted inline blocks:

  • BEFORE_TOOL fires once β€” execution_mixin.py:1254-1267, same BeforeToolInput fields. βœ…
  • Blocking preserved β€” is_blocked returns a sentinel string that the loop appends to results (chat_mixin.py), so a blocked call still yields a result entry. βœ…
    • Note: block message text changes from "[Tool blocked by hook: …]" to "Execution of {tool} was blocked by security policy." β€” this now matches the sync path (tool_execution.py), an intentional consistency improvement, not a regression.
  • modified_input mutation preserved β€” execution_mixin.py:1271-1273 (was NOT applied in the old inline path β€” this fixes a latent gap). βœ…
  • AFTER_TOOL + additional_context aggregation preserved β€” execution_mixin.py:1279-1299. βœ…
  • Non-fatal isolation β€” retained inside the pipeline; the loop keeps its own try/except around execute_tool_async. βœ…

The old inline BEFORE_TOOL also used hook_runner.execute(...) without target=function_name, whereas the pipeline passes target=function_name (:1266, :1292) β€” the retained path is strictly more correct for tool-scoped hook matching.

Phase 3 β€” Tests

tests/unit/agent/test_achat_unified_dispatch.py .......... 5 passed
tests/unit/hooks/test_hooks.py .......................... 42 passed
tests/unit/hooks/test_before_tool_definitions.py ......... 8 passed
tests/unit/plugins/test_hook_bridge.py .................. 27 passed

82 passed, 0 failed. (3 unrelated collection errors elsewhere in the suite are pre-existing β€” missing resolve_output export / install state β€” and untouched by this PR.)

Verdict

Approve. The change eliminates real double-firing of BEFORE_TOOL/AFTER_TOOL in the legacy async loop, removes a per-tool-call hot-path cost, and actually closes two latent gaps (missing modified_input application and missing target= scoping) by routing through the single guarded executor. No fixes required β€” no valid issues raised by CodeRabbit/Greptile, and none found independently.

Files modified by me: none β€” the PR is correct as-is.

Job run Β· Branch claude/issue-3455-20260727-1716

@praisonai-triage-agent praisonai-triage-agent Bot removed pipeline/blocked:cooldown Blocked: post-push or @claude cooldown pipeline/blocked:ci Blocked: CI not green on HEAD labels Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:manual-review Blocked: requires manual review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate hook firing: legacy async _achat_completion fires BEFORE_TOOL/AFTER_TOOL inline and again inside execute_tool_async

1 participant