fix(hook): ignore stale unmatched Agent tool_uses in stop-conditions#61
Merged
Merged
Conversation
Stop hook was returning decision: "allow" for the rest of a session
once any Agent tool_use went unmatched in the transcript. In SDK-driven
sessions, some Agent calls never get a tool_result the scanner expects,
so unmatched IDs from minutes/hours ago poisoned the hook indefinitely.
hasActiveBackgroundAgents now records each Agent tool_use's timestamp
and only counts unmatched IDs issued within the last 2 minutes
(configurable via { recencyWindowMs, nowMs }). Older entries are
treated as completed/abandoned.
Closes #60
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
codevibesmatter
pushed a commit
that referenced
this pull request
Apr 23, 2026
SDK-driven sessions (entrypoint: sdk-ts) emit user-typed prompts as
string-form message.content, not an array-of-blocks [{type:'text'}].
The #61 staleness heuristic in hasActiveBackgroundAgents only fires on
the array shape, so stale Agent tool_use IDs accumulate and the "allow
stop" guard false-fires for the entire session — the exact symptom
issue #60 was filed to fix.
Closed
4 tasks
codevibesmatter
pushed a commit
that referenced
this pull request
Apr 23, 2026
…pts (#68) hasActiveBackgroundAgents silently failed on SDK-driven sessions (entrypoint: sdk-ts) because those transcripts encode typed user prompts as bare strings on message.content, not as [{type:'text',text:'...'}] arrays. The for...of iterated characters, hasUserText stayed false, and the #61 staleness clear never fired — stale Agent tool_use IDs persisted forever and the guard false-allowed exit despite canExit=false. Three fixes: - Normalize message.content: strings become [{type:'text',text:content}] via normalizeContentBlocks(). Restores the #60/#61 staleness clear for SDK sessions. - 120s recency window: unmatched Agent tool_use IDs whose transcript timestamp is past the window are abandoned. Covers fire-and-forget SDK orchestrators that never send a second user prompt. Timestamp missing → entry falls back to staleness-clear only (back-compat). - Recognize "Task" tool name alongside "Agent" — older CC (≤2.1.50) and the eval harness allowedTools emit name:"Task" for subagents. hasActiveBackgroundAgents now takes an optional `now` parameter for deterministic test time. 8 new tests cover: SDK string-form user prompt, Task-named subagent, recency filter (inside/outside/boundary), and the full issue #60 duraclaw scenario (stale agents from 7h ago + string prompts → guard returns false).
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
hasActiveBackgroundAgentsnow timestamps each Agent tool_use and only counts unmatched IDs issued within the last 2 min (configurable). Stale entries are treated as completed/abandoned.decision: "allow".Test plan
bun test src/commands/hook.test.ts— 13/13hasActiveBackgroundAgentstests pass (7 pre-existing + 5 new + empty-transcript). 8 unrelated failures (logHook/handleModeGate) pre-exist on baseline.bun test src/: 404 pass / 37 fail / 1 error vs. baseline 399 / 37 / 1 → +5 new tests, zero regressions.bun run typecheckclean.false).Closes #60