[Bug] Stop hook repeatedly triggers with "full milestone completion" demand in loop sessions
Status Open
Reported on v2.1.281
Maintainer reply None cached
Activity 3 comments · opened Sep 28, 2026
Bug Description
│ │ What happened: In a long autonomous /loop-style session (goal: complete a project milestone), the Stop hook fired ~10 times in a row with near-identical fee…Stop-hook keeps demanding "full milestone completion" despite repeated, verified evi…
Environment Info
- Platform: darwin
- Terminal: Superconductor
- Version: 2.1.281
- Feedback ID: cd4c2311-63d1-420b-910a-2c69eb220ad9
Errors
[{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-25T00:58:59.590Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-25T00:59:00.580Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-25T01:02:17.859Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-25T14:24:27.330Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-26T04:11:10.878Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74061)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)","timestamp":"2026-09-26T04:11:11.080Z"},{"error":"Error: Tool bash not found\n at se (/$bunfs/root/chunk-vdm4xd3c.js:19:13554)\n at ir (/$bunfs/root/chunk-vnnj4fsp.js:18:61317)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74081)\n at In (/$bunfs/root/chunk-vnnj4fsp.js:18:73965)\n at Fh (/$bunfs/root/chunk-vnnj4fsp.js:18:74850)\n …
Note: Content was truncated.
3 Comments
If this is your own Stop hook, or one from a plugin that runs a milestone check, the input it receives has a field made for exactly this loop:
stop_hook_activeistruewhen Claude Code is already continuing because a stop hook blocked. A check that can't be satisfied should look at it and let the turn end:Claude Code also caps this at 8 consecutive continuations and then overrides the next block (
CLAUDE_CODE_STOP_HOOK_BLOCK_CAPchanges the number), which is likely why it ended after around that many rounds instead of looping forever.The other half: a Stop hook that demands "full milestone completion" will loop whenever the model can't prove completion in a form the hook recognises. It holds up better if the hook checks something mechanical (tests pass, a file exists, a command exits 0) and returns that specific failure as the reason, instead of a general demand the model can only answer with more words.
@faizjamal0111-svg Thanks, this is a good, precise fix and matches exactly what we hit. Worth naming the actual source, since it's not a custom hook: it's the bundled
ralph-loop@claude-plugins-officialplugin (v1.0.0), invoked via/ralph-loop, running in a Superconductor-managed Claude Code terminal session.Trail, for anyone else chasing this:
~/.claude/settings.json->enabledPlugins["ralph-loop@claude-plugins-official"]: true,Stophook chain resolves toagent-deck hook-handlerplus the plugin's own hook.~/.claude/plugins/cache/claude-plugins-official/ralph-loop/1.0.0/hooks/hooks.json->Stop->bash "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.sh".~/.claude/plugins/cache/claude-plugins-official/ralph-loop/1.0.0/hooks/stop-hook.sh.Two things stood out reading it against your explanation:
stop_hook_activecheck anywhere in the script. It readsHOOK_INPUT=$(cat)at the top, pullssession_idout of it for session isolation, but never inspectsstop_hook_active. Every invocation is treated as a first block, so it always re-emits{"decision":"block","reason":$prompt}unconditionally whenever the promise tag doesn't match, with no way to notice it already pushed back.``
bash
`PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; ...')
if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
# only then does it let the turn end
<promise>` tag, not "a test passed" or "a file exists." With a generic promise like "full milestone completion," that's the self-report problem you flagged, just built into the officially bundled plugin rather than a user's own hook.So "done" means the model reproduced the exact configured string inside a
What actually ended our loop was
CLAUDE_CODE_STOP_HOOK_BLOCK_CAP(default 8, per your comment), not the script's own logic -max_iterationsin the loop's state file was set past that.Given this is the officially distributed plugin, seems worth patching
stop-hook.shitself: an earlystop_hook_activeexit, and maybe steering/ralph-loop's docs toward mechanical completion promises (a command exit code, a file check) instead of a prose tag the model self-reports.Thanks for tracing it to the plugin. One correction to my own advice, now that we know it's ralph-loop: an early
stop_hook_activeexit would end every Ralph loop after one extra pass, because blocking the stop is how the plugin loops. So I wouldn't patch that in as is.What works with the plugin as it stands:
--max-iterations Nto/ralph-loopwith an N you choose.stop-hook.shthen ends the loop itself when it gets there, instead of the harness cutting it off.<promise>TESTS_GREEN</promise>only afternpm testexits 0, rather than a phrase the model has to judge for itself.CLAUDE_CODE_STOP_HOOK_BLOCK_CAPon purpose.The same issue is open upstream as anthropics/claude-plugins-official#5312. Your trail would be a useful second report there, especially that the block cap ended the loop before
max_iterationsdid.