[BUG] /goal Stop hook is skipped while a background task is live and never re-evaluated when it ends, leaving the session idle indefinitely
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
/goal <condition> installs a session-scoped Stop hook that is supposed to block stopping until the
condition holds. In practice it is only invoked on stops where the session has zero live background
tasks. If any background task is registered as running when the turn ends, the goal hook does not run,
the stop is not blocked, and the session goes idle.
The damaging half is what happens next: when those background tasks later terminate, the goal is never
re-evaluated. Their completion notifications are recorded, but produce no stop and no turn, so nothing
re-checks the goal. The session stays idle until a human types something.
In the session below this cost 4 hours 6 minutes of dead time on an autonomous run that was supposed
to keep working unattended. The failure is silent — nothing in the UI indicates the goal has stopped
being enforced.
Evidence across all 28 stops in that session, cross-referenced against how many background tasks were
live at each:
| Goal hook | Stops | Live background tasks |
| --- | --- | --- |
| ran | 4 | 0 on every one |
| skipped | 24 | ≥1 on all but one |
The four stops where the goal hook ran are the only four with nothing running. A time-based cooldown is
ruled out: stops at 22:43, 23:04, 23:32 and 23:55 were all more than 40 minutes after the previous goal
evaluation at 22:19 and still skipped it — every one of them had live background work.
The one apparent exception is the stop that stranded the session, and only because of where the evidence
lives: two background shells were live, but they had been launched by subagents, whose tool calls are
written to subagents/agent-*.jsonl rather than the main transcript. The UI showed them correctly
("· 2 shells still running"); only a main-transcript reconstruction misses them.
What Should Happen?
Either:
- The goal hook is evaluated on every stop, including stops with live background work; or
- If skipping is deliberate — the session expects to be re-invoked when the background task notifies —
then the goal is re-evaluated when that task reaches a terminal state, so the skip is a deferral
rather than a permanent loss.
Today it is neither: the evaluation is skipped and never retried. A single background task that ends
without waking the loop permanently disarms the goal for the rest of the session.
At minimum, if a goal is armed and a stop is allowed anyway, that should be surfaced. A silent disarm is
what turned this into hours of dead time instead of a five-second correction.
Error Messages/Logs
# stop_hook_summary recorded at 01:40:20 — the turn that went idle.
# Only the unrelated local notification hook ran; the goal hook is absent.
{
"subtype": "stop_hook_summary",
"hookCount": 1,
"hookInfos": [ { "command": "~/.claude/hooks/stop-notify.sh", "durationMs": 146 } ],
"hookErrors": [],
"preventedContinuation": false,
"stopReason": "",
"version": "2.1.220"
}
# The goal registered earlier in the same session (condition redacted):
"A session-scoped Stop hook is now active with condition: \"<redacted>\"."
# The two background shells that were live at that stop, both launched by subagents.
# Each was killed ~60 minutes after launch; neither kill produced a stop or a turn.
<task-notification>
<task-id>bg1s6uljs</task-id>
<status>killed</status>
<summary>Background command "Wait for hygiene agent completion" was stopped</summary>
</task-notification>
<task-notification>
<task-id>bfgjwpzkc</task-id>
<status>killed</status>
<summary>Background command "Wait for the workspace test to finish" was stopped</summary>
</task-notification>
# No stop_hook_summary of any kind exists between 01:40:20 and 05:48:24.
Steps to Reproduce
- Start a session and run
/goal <a condition that is not yet true>. Confirm the acknowledgement
("A session-scoped Stop hook is now active with condition: …").
- Have the model launch a background task that outlives the turn — e.g.
Bashwith
run_in_background: true running a poll loop with no timeout:
``bash``
until [ -s /path/that/never/appears ]; do sleep 30; done
In the observed case these were launched by subagents, but a directly-launched background command
should reproduce it just as well.
- Let the model end its turn while that background task is still registered as running.
- Observe: the goal hook does not run. In the transcript, the
stop_hook_summaryfor that turn has
hookCount: 1 (or 0 if no other Stop hook is configured), does not list the goal condition, and has
preventedContinuation: false. The turn ends.
- Wait for the background task to finish or be killed.
- Observe: its
<task-notification>is recorded, but no stop fires and no turn starts. The session
stays idle indefinitely until a human types something.
Configuring any second Stop hook (a one-line script) makes step 4 much easier to see, since the goal
hook's presence or absence in hookInfos becomes visible by contrast.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.220 (Claude Code)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Other
Additional Information
Timeline
| Time | Event |
| --- | --- |
| 01:20:55 | Subagent A launches background poll loop bg1s6uljs ("Wait for hygiene agent completion"), run_in_background: true, no timeout |
| 01:22:11 | Subagent A itself reports completed — 76 s later. Its watchdog keeps running |
| 01:27:15 | Subagent B launches background poll loop bfgjwpzkc ("Wait for the workspace test to finish"), same shape |
| 01:32:50 | Subagent B reports completed. Its watchdog keeps running |
| 01:40:20 | Turn ends. hookCount: 1, goal hook skipped, preventedContinuation: false |
| 02:20:57 | bg1s6uljs → status: killed (60 min 2 s after launch) |
| 02:27:15 | bfgjwpzkc → status: killed (exactly 60 min after launch) |
| — | No stop_hook_summary, no assistant turn, between 01:40:20 and 05:48:24 |
| 05:46:03 | Human types continue; work resumes normally |
Contributing factor — possibly worth its own issue
Background commands launched by a subagent outlive the subagent. Both shells here were still
registered as live long after their parent agents had reported completed — 58 and 54 minutes
respectively — and were cleared only by what looks like a 60-minute cap on background commands (both
killed within 2 seconds of their sixtieth minute).
Whether or not the /goal behaviour changes, a background command whose owning subagent has finished
seems like it should be reaped with its parent rather than continuing to count as live session work.
Filed here as context rather than as a second request, per the single-bug-per-report rule.
Suggested fixes
- Re-evaluate the goal condition whenever a background task reaches a terminal state
(completed / failed / killed) and no further tasks are live. This alone closes the hole.
- Alternatively, evaluate the goal hook on every stop regardless of background state.
- Reap subagent-owned background commands when their owning subagent completes.
- Surface the skip in the UI when a goal is armed and a stop is allowed anyway.
How this evidence was gathered
All of it comes from the session's own JSONL transcript plus the per-subagent transcripts under~/.claude/projects/<project>/<session>/subagents/: stop_hook_summary rows for hook invocations,<task-notification> blocks for task lifecycle, and tool_use records for the launches. Project paths,
ticket identifiers and internal names are redacted; nothing else has been altered.
One caveat, stated plainly
The mechanism claim — "skipped because background work was live" — is inference from a 27-of-28
correlation, not from reading the implementation. The correlation is shown above so it can be confirmed
or corrected against the actual gating logic.
Similar tickets
Directly relevant
https://github.com/anthropics/claude-code/issues/82546
[BUG] /goal set at a compact_boundary never starts its turn; session idles silently for hours with "active" still displayed
Open · 2026-07-30 · no labels · 1 comment · reported on 2.1.220
Same symptom as this. The comment names a second trigger — end_turn mid-work without compaction — and concludes "the goal loop not re-arming after an end_turn it should have overridden."
https://github.com/anthropics/claude-code/issues/59584
[DOCS] /goal docs do not explain evaluator waits for background shells or delegated subagents
Open · 2026-05-16 · labels: documentation, enhancement, area:docs · 3 comments
Says the wait is deliberate, citing changelog v2.1.143 "Fixed /goal evaluator firing while background shells or delegated subagents are still running." I could not verify that line — the public CHANGELOG.md retains only back to 2.1.178.
Same feature, different failure
https://github.com/anthropics/claude-code/issues/73674
/goal completion Stop hook re-matches phrases from earlier in the transcript forever, preventing clean session termination
Open · 2026-07-03 · labels: bug, area:hooks, platform:macos · 0 comments
https://github.com/anthropics/claude-code/issues/78121
Stop hook re-fires despite stop_hook_active: true, causing /goal loop to spin after completion
Open · 2026-07-16 · labels: bug, has repro, area:hooks
The rest of the /goal Stop hook family
Titles below come from the search listing rather than fetching each issue, so they may be lightly abbreviated. None match this bug — they're evaluator prompt-size, output-format and loop problems.
Open:
- https://github.com/anthropics/claude-code/issues/62246 — [BUG] /goal Stop hook emits markdown to stdout, fails JSON schema validation
- https://github.com/anthropics/claude-code/issues/58192 — /goal Stop hook fails with "Prompt is too long" when goal text is large
Closed:
- https://github.com/anthropics/claude-code/issues/67665 — goal, eternal loop with exit hooks and potentially catastrophic outcome
- https://github.com/anthropics/claude-code/issues/58516 — /goal hook fires "Goal Achieved" while TaskList tasks still pending/in_progress
- https://github.com/anthropics/claude-code/issues/58348 — [BUG] /goal stop hook entersrences unregistered skills
- https://github.com/anthropics/claude-code/issues/58558 — [BUG] /goal Stop hook fails when returning markdown-fenced JSON
- https://github.com/anthropics/claude-code/issues/63453 — [BUG] /goal stop hook fires after every assistant turn, interrupting user mid-message
- https://github.com/anthropics/claude-code/issues/60966 — /goal Stop hook fails with "Prompt is too long" in long sessions
- https://github.com/anthropics/claude-code/issues/58970 — [Bug] Anthropic API Error: Phook
- https://github.com/anthropics/claude-code/issues/58712 — /goal stop hook: 'JSON validcontains @file reference
- https://github.com/anthropics/claude-code/issues/65416 — Active /goal Stop hook overrinterrupt + handoff request)
- https://github.com/anthropics/claude-code/issues/63674 — Stop hook loops with no userective/unverifiable
- https://github.com/anthropics/claude-code/issues/62345 — /goal stop hook "Prompt is t sessions with 1M-context model
Background-work adjacent, not /goal
- https://github.com/anthropics/claude-code/issues/64901 — [BUG] Async sub-agent complecache (open)
- https://github.com/anthropics/claude-code/issues/72221 — macOS arm64: 2+ concurrent background agents cause daemon supervisor 'signal war' (open)
- https://github.com/anthropics/claude-code/issues/74142 — [Feature] Per-agent and per-ol (open)
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗