Native /goal Stop hook re-fires indefinitely with no way to acknowledge a hold

Status Open
Reported on v2.1.258
Maintainer reply None cached
Activity 4 comments · opened Sep 13, 2026

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:

  1. 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.

  1. During ordinary autonomous work with no hold in effect, a /goal condition 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

  1. Set a /goal with a multi-part condition in a long-running autonomous session.
  2. Complete or supersede part of that condition while continuing other work, or begin a

deliberate hold (for example, waiting on a scheduled compaction).

  1. Observe the Stop hook re-fire with unchanged framing, unaffected by the completed work, an

explicit correction, or the deliberate hold.

  1. 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 — /goal set at a compact boundary never starts its turn; a related but distinct

interaction between /goal and compaction.

  • #83266 — /goal Stop 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 — /goal Stop hook repeatedly re-fires after the user accepts a blocked outcome.
  • #93744 — /goal Stop 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.

View original on GitHub ↗

3 Comments

AdamiecRadek · 27 days ago

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.

drewgillson · 19 days ago

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

  • The user set a goal with /goal <long condition>. This installed a session-scoped Stop hook that blocks stopping until the condition holds.
  • Mid-run, with subagents active, the user sent: "Please pause your work until my session limit resets in 2 and a half hours".
  • The assistant stopped its background subagents, scheduled a one-shot CronCreate wakeup 2.5 hours out, and replied that it was pausing.
  • The Stop hook then evaluated the goal, found it unmet, and blocked the stop with its evaluation text. The assistant replied that it was paused at the user's request. The hook fired again with essentially identical evaluation text. This repeated seven times in a row, each firing a fresh model turn, with the assistant's replies shrinking to one line each.
  • There is no tool the model can call to pause, suspend, or clear the goal; only the user can run /goal clear. The hook's own guidance says /goal clear is only for clearing early and not to tell the user to run it after success, which does not cover a pause.
  • The user's stated purpose for pausing was to stop consuming their session limit. Every hook re-fire consumed more of it, the opposite of what they asked for.
  • The hook evaluator recognized the pause each time. Its feedback quoted "Work was explicitly paused at [the user]'s request (session limit)" and the scheduled resume time, but still blocked, because the condition is evaluated purely on completion.

Expected behavior (any of these would do)

  • A user message that explicitly pauses or defers work should suspend the goal's Stop hook until the model resumes (for example when a scheduled wakeup fires), or
  • The model should have a tool to pause/suspend/clear the active goal, or
  • The hook should back off (stop re-firing) after the model gives the same "paused at the user's request" reason N times, or when a scheduled wakeup for the same goal already exists.

Reproduction

  1. /goal <any multi-step condition that cannot be met in one turn>
  2. Let the assistant start work.
  3. Send: "Please pause your work until my session limit resets in 2 hours".
  4. Observe the Stop hook re-fires on every assistant reply that says it is paused.

Environment

  • Claude Code 2.1.283 (CLI)
  • macOS, Darwin 25.6.0
  • Model: claude-fable-5-1
  • Session id: 23a47687-dbe4-4570-8ae6-aaf87b96d178
  • Timestamp: 2026-09-25, approximately 17:10 to 17:25 MDT
dannybyrnes · 18 days ago

Another 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:

"The user explicitly said 'you can cancel the loop' ... The condition to 'keep iterating ...' has been halted by the user's direct instruction to stop, making the condition no longer satisfied."

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 /goal in the desktop app does not show the clear hint. #85122's resolution says /goal with no arguments shows "/goal clear to stop early". In the desktop app it printed only this:

Goal active: <condition> (24 turns)
Last check: <evaluator reason>

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 stop worked (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.

Showing cached comments. Read the full discussion on GitHub ↗