mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 00:56:30 +03:00
Fix Stop-hook loop: honor stop_hook_active per Claude Code contract
The Stop deep pass (runStopHook) never read the stop_hook_active field from the Claude Code Stop-hook event. When a prior fire kept the turn alive via hookSpecificOutput.additionalContext and the agent legitimately declined to act, the hook re-scanned and re-blocked every re-invocation until Claude Code's consecutive-block cap force-ended the turn (issue #400). Read stop_hook_active early in runStopHook, right after the event is parsed and before any scan, and exit 0 with no output when it is true. The prior fire already surfaced the findings; acting on them is the agent's call. Only Claude Code sends this field, so the strict === true is a no-op for other harnesses. runHook (PostToolUse) and hook-before-edit.mjs (PreToolUse) never receive the field, so they are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9b7f7ffbba
commit
47aff2e0be
@@ -1960,6 +1960,20 @@ export async function runStopHook({ stdinJson, env = {}, cwd = process.cwd(), no
|
||||
return result({ skipped: 'stdin-empty', durationMs: Date.now() - started });
|
||||
}
|
||||
|
||||
// Claude Code's Stop-hook contract: `stop_hook_active` is true when this
|
||||
// hook is being re-invoked only because a prior invocation kept the turn
|
||||
// alive (here, via hookSpecificOutput.additionalContext). Re-scanning and
|
||||
// re-blocking now would loop until Claude Code's consecutive-block cap
|
||||
// force-ends the turn (issue #400). The prior fire already surfaced the
|
||||
// findings; whether to act on them is the agent's call. Exit fast with no
|
||||
// output before any scan. Only Claude Code sends this field; other
|
||||
// harnesses omit it, so the strict `=== true` is a no-op for them. This
|
||||
// guard makes the loop impossible regardless of the finding cache key's
|
||||
// line-number sensitivity (out of scope here; see findingCacheKey).
|
||||
if (event.stop_hook_active === true) {
|
||||
return result({ skipped: 'stop-hook-active', durationMs: Date.now() - started });
|
||||
}
|
||||
|
||||
const harness = resolveHarness(env, event);
|
||||
audit.harness = harness;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user