Skip to content

fix(hooks): fire PostToolUse hooks through fire_and_forget_trigger - #2575

Open
ayaangazali wants to merge 1 commit into
MoonshotAI:mainfrom
ayaangazali:fix-hooks-fire-and-forget
Open

fix(hooks): fire PostToolUse hooks through fire_and_forget_trigger#2575
ayaangazali wants to merge 1 commit into
MoonshotAI:mainfrom
ayaangazali:fix-hooks-fire-and-forget

Conversation

@ayaangazali

@ayaangazali ayaangazali commented Aug 1, 2026

Copy link
Copy Markdown

Related Issue

Resolve #2564

Description

PostToolUse and PostToolUseFailure fired their hooks with a bare asyncio.create_task(...) and then dropped the handle on the next line. Two problems with that:

  1. asyncio only keeps tasks in a WeakSet, so a pending hook task that nothing else references can be collected before its subprocess finishes. This is the footgun the CPython docs warn about.
  2. The inline done callback (lambda t: t.exception() if not t.cancelled() else None) retrieved the exception purely to silence the "never retrieved" warning, so a hook that failed did so silently.

HookEngine.fire_and_forget_trigger already exists for exactly this pattern, its docstring describes this bug, and SubagentStop in subagents/runner.py already uses it. It holds a strong reference in _pending_fire_and_forget until the task completes and logs failures via _log_fire_and_forget_failure. These two call sites just were not migrated. So this is a reuse of what is already here rather than anything new.

One honest caveat: I could not build a test that deterministically reproduces the collection itself. I tried forcing gc.collect() while a real hook subprocess was mid-flight and the hook still completed on unpatched code, since the task stays reachable through the loop's callback chain in that window. So the collection is a real but nondeterministic race, and I did not want to commit a test that passes for the wrong reason. The test I did add asserts the contract that actually differs: both hook events now route through the tracked helper. It fails on unpatched code for both events and passes after. The silent-failure-logging half of the fix is unconditional either way.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue, if any.
  • I have added tests that prove my fix is effective or that my feature works. (see the caveat above on what exactly is asserted)
  • I have run make gen-changelog to update the changelog. (hand-edited CHANGELOG.md in the same style, I do not have the Kimi API setup the skill needs)
  • I have run make gen-docs to update the user documentation. (nothing in docs describes this internal behavior)

for transparency: freshman, and the issue author had already traced this one well so most of the credit is theirs. i verified the two call sites and the helper at HEAD myself before touching anything, and talked through the asyncio task lifetime bits with claude code since that part is genuinely subtle. if the test framing is not what you want here i am happy to change it :)


Open in Devin Review

Both PostToolUse and PostToolUseFailure fired their hooks with a bare
asyncio.create_task whose handle was dropped on the next line. asyncio
only keeps tasks in a WeakSet, so a pending hook task held by nothing
else can be collected before its subprocess finishes, and the inline
done callback swallowed exceptions without logging them.

HookEngine.fire_and_forget_trigger already exists for exactly this and
is what SubagentStop uses: it holds a strong reference until the task
completes and logs failures.
Copilot AI review requested due to automatic review settings August 1, 2026 07:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(hooks): PostToolUse / PostToolUseFailure tasks collected by GC before completion

2 participants