Skip to content

fix(hook): ignore stale unmatched Agent tool_uses in stop-conditions#61

Merged
codevibesmatter merged 1 commit into
mainfrom
feature/60-stop-hook-stale-agents
Apr 16, 2026
Merged

fix(hook): ignore stale unmatched Agent tool_uses in stop-conditions#61
codevibesmatter merged 1 commit into
mainfrom
feature/60-stop-hook-stale-agents

Conversation

@codevibesmatter
Copy link
Copy Markdown
Owner

Summary

  • hasActiveBackgroundAgents now timestamps each Agent tool_use and only counts unmatched IDs issued within the last 2 min (configurable). Stale entries are treated as completed/abandoned.
  • Eliminates the perma-poison failure mode where one unmatched Agent ID early in a session caused every subsequent Stop hook to fire decision: "allow".

Test plan

  • bun test src/commands/hook.test.ts — 13/13 hasActiveBackgroundAgents tests pass (7 pre-existing + 5 new + empty-transcript). 8 unrelated failures (logHook / handleModeGate) pre-exist on baseline.
  • Full bun test src/: 404 pass / 37 fail / 1 error vs. baseline 399 / 37 / 1 → +5 new tests, zero regressions.
  • bun run typecheck clean.
  • Issue bug(hook): stop-conditions false-allows exit due to stale unmatched Agent tool_uses #60 reproduction encoded as a literal unit test (3 stale unmatched Agents from 02:54-03:01 UTC + matched recent Agent → returns false).

Closes #60

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 codevibesmatter merged commit 01b233b into main Apr 16, 2026
1 check failed
@codevibesmatter codevibesmatter deleted the feature/60-stop-hook-stale-agents branch April 16, 2026 11:33
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.
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).
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.

bug(hook): stop-conditions false-allows exit due to stale unmatched Agent tool_uses

1 participant