[BUG] /goal evaluator fires during background-shell wait windows, fragmenting agent execution
Summary
The /goal evaluator currently treats the gap between a background-shell
spawn and its completion notification as a turn boundary, firing a goal
check during the wait window. The agent is not idle in this state — it
has explicit pending work (a long-running Bash invocation started withrun_in_background=true). The hook injects repeated "goal status"
reminders into the agent's context, fragmenting execution and producing
a stream of noise the agent may erroneously respond to instead of
continuing to wait.
This is a trigger-timing bug, distinct from existing /goal issues
that focus on what happens after the hook fires.
Distinction from related issues
| Issue | Focus | Relation to this report |
|---|---|---|
| #58550 | no circuit breaker — once stuck, burns tokens indefinitely | independent — this report is about whether the hook should fire at all in this state |
| #58516 | hook reaches the wrong conclusion (Goal Achieved while TaskList has pending items) | independent — noise is harmful regardless of conclusion |
| #58348 / #58465 | infinite loops from unsatisfiable conditions / ignored overrides | independent — but the same trigger-timing root cause amplifies all of these |
Fixing trigger-timing here would incidentally mitigate #58550 and
#58516 by reducing how often the evaluator runs in states where its
output is meaningless or wrong.
The agent has three distinct states — the hook conflates two of them
| State | When | Turn truly ended? | Hook should fire? |
|---|---|---|---|
| Tool-use in-flight | agent emitted tool_use, awaiting result | No | No (currently does not — correct) |
| Background-tool wait | agent spawned a long-running shell with run_in_background=true; spawn returned, but the work is incomplete and the agent is awaiting the completion notification | No — agent still has live, intentional pending work | No — but the hook does fire. This is the bug. |
| Truly idle | agent has finished its intent and is awaiting fresh user input | Yes | Yes |
The hook appears to treat the absence of a new tool_use as evidence
the agent is idle, collapsing state 2 into state 3.
Repro (conceptual)
/goal <any non-trivial goal>- Have the agent launch a long-running background shell, e.g.:
npm run devpytest tests/ --runslow(multi-minute run)- Any build / training / migration of several minutes
- Observe the goal evaluator firing during the wait window — once per
evaluator cycle — while the shell is still running and the agent is
doing nothing but waiting for the completion notification.
- Each fire injects a "goal status" reminder. The agent may:
- respond to the reminder instead of continuing to wait
- re-poll the background shell prematurely
- drift to unrelated actions
- in any case, fragment the planned execution flow
Expected
The evaluator fires only when the agent is truly idle — after the
agent's final response with no pending background work the harness is
tracking. Background-tool wait windows should be transparent to the
evaluator.
Why it matters
- Tokens — each fire reprocesses full context. #58550 quantified
~50% weekly budget burn from analogous over-firing.
- Agent behavior — repeated reminders during a wait can cause the
agent to abandon the wait, poll early, or branch into unrelated
work, defeating the point of run_in_background.
- UX — long-running commands now generate visible hook-fire noise.
- Cascading risk — more fires → higher probability of #58516
false-positive Goal Achieved.
Note
This report is mechanism-level and does not include a captured
transcript. Any sufficiently long run_in_background Bash invocation
under an active /goal should reproduce. Happy to add a captured
transcript if maintainers want one.
Environment
- Reporter platform: Windows 11
- Model: claude-opus-4-7
- Claude Code version: 2.1.140
6 Comments
Found 2 possible duplicate issues:
This issue will be automatically closed as a duplicate in 3 days.
🤖 Generated with Claude Code
Confirming this on macOS with a captured transcript — happy to provide the data the author offered.
Environment
4d8896f1-98b2-499d-8cbb-0676b04622a5git worktreecheckouts; communication via async git commits.This adds two things to the report:
1. Quantitative timing from a real session
| Metric | Value |
|---|---|
| Loop window | 24 min 1 s (11:33–11:57 UTC, single session) |
|
stop_hook_summaryfires | 25 ||
goal_statusattachments | 22, allmet: false|| Median inter-fire interval | 9.0 s |
| Min / max inter-fire interval | 3.3 s / 776.8 s |
| Forced no-op turns produced (literal
等。/ "wait." single-character messages) | 15 ||
preventedContinuationflag on each |false(hook didn't block — it re-fired amet:falseattachment that forced a new turn) |2. The evaluator's own
reasonfield self-incriminatesgoal_statusattachments carry an LLM-generatedreason. Multiple instances in this session contain phrases like:The evaluator already identified the wait state and chose to emit
met:falseanyway. There is noidle:trueshort-circuit. This is the cheapest single-point fix: when the evaluator's own reasoning recognizes a wait state (or the previous assistant turn is ≤ ~8 tokens), emitmet: indeterminateinstead ofmet: false. Applied to this session, this alone would have eliminated all 15等。no-op turns.3.
run_in_background: trueis confirmed to not work as a user-level workaroundThe main agent attempted exactly the escape the OP described —
until [...]; do sleep 45; donewithrun_in_background: true,timeout: 600000. The bash tool returns"Running in the background"immediately, the Stop boundary is reached again within ~3 seconds, and/goalre-fires. The only working escape is foreground blocking bash, capped at the 10-minuteBash.timeoutceiling — insufficient for many real orchestration runs.4. Strengthens #58550 / #58516
This data quantifies #58550's "burns unlimited tokens" concern (~9 s median interval ≈ 6.7 forced fires per minute under sustained wait), and #58516's "wrong conclusion" risk is amplified by the high firing frequency observed here.
Suggested fix order (additive to the OP's expected behavior)
goal_statusevaluator as described above. Eliminates the worst behavior with no API surface change.--suspended-while <predicate>option on/goal, e.g. suspend while declared worktrees have no new commits past base.run_in_background(or a dedicatedWaittool) that suppresses Stop hooks for the lifetime of the underlying process./goalre-evaluation offSubagentStop/ filesystem events instead of main-agent Stop boundaries — the architecturally correct fix for the orchestration use case.Sanitized full evidence bundle (timing distribution, JSON record samples for
goal_statusandstop_hook_summary, token counts, repro) available on request. Can also share a redacted session jsonl with maintainers privately.Adding the evidence proactively rather than waiting on a request. Also: I separately submitted
/feedbackfrom inside the affected session (4d8896f1-98b2-499d-8cbb-0676b04622a5), so Anthropic internal triage has the raw session context available end-to-end if helpful.Sanitized evidence slice attached as a secret gist (5 record types only —
goal_status×22,stop_hook_summary×25, no-op等。assistant turns ×14, representative Bash tool_use ×2 covering the failedrun_in_backgroundworkaround and worktree status polling, aggregate token usage):https://gist.github.com/bugbubug/213e95c81dd69f782d8be71ff8dd9235
Sanitization rules applied (deterministic regex, post-write leak-scan passed clean):
polyquantretainedHappy to share the full unredacted jsonl with maintainers via a private channel if helpful (it contains business-logic context that is not safe to publish but can be sent to a trusted address).
Thanks @bugbubug — the quantitative timing data (25 fires in 24 min,
~9s median interval, 15 forced
等。no-op turns) and theself-incriminating
reasonfield excerpt are exactly the evidence Icouldn't include. The detail that the evaluator's own reasoning
already recognizes the wait state ("Last assistant message stated
'waiting for subagent completion before drafting retro-W1'") and emits
met: falseanyway is the strongest single argument for the proposedidle short-circuit. The negative result on
run_in_background+ 600stimeout is also load-bearing — it eliminates the obvious user-level
workaround.
---
@github-actions — the two dedup candidates appear to be keyword
overlap, not mechanism overlap:
from live background subprocesses (UI display logic,
pgrep-basedinference).
sleeping/looping agents into the "Ready for Review" section (UI
section assignment).
Both are surface-level UI state-rendering bugs in the agents view.
This issue is about the
/goalevaluator's trigger boundary —whether to fire a model-side evaluation at all when the agent sits
between a background-shell spawn and its completion. Different
subsystem, different fix surface, no shared code path. Please do not
auto-close.
+1
Currently
/goaldoesn't:It keeps forcing the primary agent to work.
<img width="1305" height="624" alt="Image" src="https://github.com/user-attachments/assets/f97b8835-7cd2-49f4-9d5e-c8215e6e1266" />
This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.