Native /goal Stop hook re-fires indefinitely with no way to acknowledge a hold
Summary
Bug: a session-scoped /goal Stop hook can re-fire indefinitely with unchanged or stale text,
even after the assistant provides verifiable evidence the condition is met, or after the session
enters a deliberate hold. Only the built-in repeated-block safety valve ends the loop, and the
pattern resumes on a later turn.
Expected behavior
The /goal evaluator should recognize live transcript evidence that its condition is met, or
provide a supported way for a session to acknowledge an intentional hold (for example, an
in-flight compaction) without triggering an unbounded re-fire loop.
Actual behavior
Observed independently in four sessions across two trigger conditions:
- A session entered a deliberate hold while waiting on a scheduled context compaction. A live
/goal condition re-fired at least nine consecutive times with the same holding text, until
the repeated-block safety valve intervened.
- During ordinary autonomous work with no hold in effect, a
/goalcondition re-fired at least
21 consecutive times quoting a stale multi-part goal. Two separate in-transcript corrections
citing concrete evidence that the named work had already completed did not change the
re-fired text.
Minimal reproduction
- Set a
/goalwith a multi-part condition in a long-running autonomous session. - Complete or supersede part of that condition while continuing other work, or begin a
deliberate hold (for example, waiting on a scheduled compaction).
- Observe the Stop hook re-fire with unchanged framing, unaffected by the completed work, an
explicit correction, or the deliberate hold.
- Continue until "A hook blocked the turn from ending 9 consecutive times" appears. Observe the
pattern resume on a later turn.
Environment
- Product and version: Claude Code CLI, at least 2.1.258, pattern recurring through later
2.1.26x builds.
- OS and version: Linux (Fedora 44), also observed on other Linux hosts.
- Model / resolved model ID: not applicable; independent of the active model.
- Runtime context: tmux-hosted CLI session, both fresh and resumed-after-compaction.
- Authentication/provider path: subscription and API key paths both observed.
Diagnostics
No crash and no error text are produced; the only signal is the repeated identical Stop hook
re-fire and, eventually, the built-in "hook blocked the turn from ending 9 consecutive times"
override message.
Regression and controls
- Fresh process/session: reproduces in both a fresh session and one resumed after compaction.
- Last known good version: unknown; the pattern has been present across every build checked.
- Relevant control: every locally-registered Stop hook was independently tested against a
realistic Stop payload and each exited cleanly with no output, ruling out a local hook as the
cause.
Hypothesis
The prompt-type Stop hook evaluator does not appear to re-read live transcript state when
judging whether its condition is met, and there is no supported mechanism for a session to
signal an intentional hold without continuing to be re-invoked.
Related public reports
- #82546 —
/goalset at a compact boundary never starts its turn; a related but distinct
interaction between /goal and compaction.
- #83266 —
/goalStop hook skipped while a background task is live and never re-evaluated
after it ends.
- #78121 (closed) — Stop hook re-fires despite
stop_hook_active: true. - #91601 (closed) — Stop-hook goal-condition check re-fires identically forever, ignoring an
explicit stand-down.
- #92242 —
/goalStop hook repeatedly re-fires after the user accepts a blocked outcome. - #93744 —
/goalStop condition evaluator cannot see the instruction passed via/goal, loops
until it declares itself unachievable.
None of the above describes the specific pattern in this report: unchanged re-fire that persists
across an explicit in-transcript evidentiary correction, and across a deliberate hold state such
as waiting on a scheduled compaction.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
This looks like the same gap. In a session I ran, the goal evaluator has exactly three verdicts: met, not yet met, and impossible. None of them means the condition is correctly waiting on something only the user can give, so a goal whose last step needs the user’s own action gets not yet met on every single check, forever.
On my run this cost nine blocked stops, twice in one session, before the built-in safety valve stepped in both times. The evaluator was reading the live conversation correctly each time and giving an accurate reason each time, so this does not look like stale state to me. It looks like there is no verdict available for a stopping point that is genuinely correct.
I would propose a fourth verdict, something like blocked on the user, gated by the same evidence the evaluator already needs for met: the transcript has to show the session actually asked and that nothing else in the condition is outstanding. With that verdict, the turn can end without pausing or failing the goal, and evaluation picks back up on the user’s next message.
A workaround exists today, writing every goal condition so it never resolves on a step the user has to take, but that only holds if every person setting a goal remembers to phrase it that way every time, so it will not hold up at scale.
Another instance of the "deliberate hold" pattern, this time where the hold was an explicit user request to pause. Would have titled it: /goal stop hook loops when the user asks to pause; model has no way to suspend the goal
What happened
/goal <long condition>. This installed a session-scoped Stop hook that blocks stopping until the condition holds./goal clear. The hook's own guidance says/goal clearis only for clearing early and not to tell the user to run it after success, which does not cover a pause.Expected behavior (any of these would do)
Reproduction
/goal <any multi-step condition that cannot be met in one turn>Environment
Darwin 25.6.0Another instance, this time with an explicit cancel, not a pause, in the desktop app. It adds three details not in this thread.
1. The evaluator recognised the cancel and still blocked. After I wrote "you can cancel the loop", every check's reason said the user had cancelled, then treated that as not met:
This is the verdict gap @AdamiecRadek describes. There is no outcome for "the user withdrew the goal", so a withdrawn goal reads as "not yet met" forever.
2. The goal survived quitting and relaunching the app. I quit the Claude desktop app to escape the loop. On relaunch the session resumed, and the Stop hook fired again on the first reply. I also checked
~/.claude: no settings file or hook file holds the goal, so neither I nor the model could remove it from the shell.3. Bare
/goalin the desktop app does not show the clear hint. #85122's resolution says/goalwith no arguments shows "/goal clear to stop early". In the desktop app it printed only this:There was no mention of
clear. The model, having no reference to the clear syntax in context, told me to run/goal, so I ran it twice and nothing changed. Only/goal stopworked (Goal cleared: ...). The status line also counted 24 turns, well past the 8-consecutive-block safety valve.Suggested fix, in line with this thread: treat an explicit cancel or stop from the user as clearing the goal, or give the evaluator a "withdrawn by user" verdict that clears it. At the very least, have the model and the status panel both name
/goal clear.Environment: Claude desktop app 2.9939.2 (Code tab), bundled Claude Code 2.1.281, macOS (Darwin 25.5.0), claude-opus-5-5.