[BUG] /goal evaluator fires during background-shell wait windows, fragmenting agent execution

Status Fixed / completed
Reported on v2.1.140
Maintainer reply None cached
Activity 6 comments · opened May 13, 2026 · closed May 14, 2026

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 with
run_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)

  1. /goal <any non-trivial goal>
  2. Have the agent launch a long-running background shell, e.g.:
  • npm run dev
  • pytest tests/ --runslow (multi-minute run)
  • Any build / training / migration of several minutes
  1. 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.

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

  1. Tokens — each fire reprocesses full context. #58550 quantified

~50% weekly budget burn from analogous over-firing.

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

  1. UX — long-running commands now generate visible hook-fire noise.
  2. 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

View original on GitHub ↗

6 Comments

github-actions[bot] · 3 months ago

Found 2 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/58297
  2. https://github.com/anthropics/claude-code/issues/58381

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

bugbubug · 3 months ago

Confirming this on macOS with a captured transcript — happy to provide the data the author offered.

Environment

  • Claude Code 2.1.140 / macOS 26.0.1 / Node v24.15.0 / Opus 4.7 (1M)
  • Session under analysis: 4d8896f1-98b2-499d-8cbb-0676b04622a5
  • Variant: multi-agent worktree orchestration (not just a single long-running background shell). Main agent dispatched 3 parallel out-of-process subagent harnesses into git worktree checkouts; 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_summary fires | 25 |
| goal_status attachments | 22, all met: 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 |
| preventedContinuation flag on each | false (hook didn't block — it re-fired a met:false attachment that forced a new turn) |

2. The evaluator's own reason field self-incriminates

goal_status attachments carry an LLM-generated reason. Multiple instances in this session contain phrases like:

"Last assistant message stated 'waiting for subagent completion before drafting retro-W1'"

The evaluator already identified the wait state and chose to emit met:false anyway. There is no idle:true short-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), emit met: indeterminate instead of met: false. Applied to this session, this alone would have eliminated all 15 等。 no-op turns.

3. run_in_background: true is confirmed to not work as a user-level workaround

The main agent attempted exactly the escape the OP described — until [...]; do sleep 45; done with run_in_background: true, timeout: 600000. The bash tool returns "Running in the background" immediately, the Stop boundary is reached again within ~3 seconds, and /goal re-fires. The only working escape is foreground blocking bash, capped at the 10-minute Bash.timeout ceiling — 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)

  1. (cheap) Idle short-circuit in the goal_status evaluator as described above. Eliminates the worst behavior with no API surface change.
  2. (small) Surface a --suspended-while <predicate> option on /goal, e.g. suspend while declared worktrees have no new commits past base.
  3. (medium) Truly blocking variant of run_in_background (or a dedicated Wait tool) that suppresses Stop hooks for the lifetime of the underlying process.
  4. (clean) Drive /goal re-evaluation off SubagentStop / 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_status and stop_hook_summary, token counts, repro) available on request. Can also share a redacted session jsonl with maintainers privately.

bugbubug · 3 months ago

Adding the evidence proactively rather than waiting on a request. Also: I separately submitted /feedback from 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 failed run_in_background workaround and worktree status polling, aggregate token usage):

https://gist.github.com/bugbubug/213e95c81dd69f782d8be71ff8dd9235

Sanitization rules applied (deterministic regex, post-write leak-scan passed clean):

  • Project name polyquant retained
  • Internal milestone codename, internal crate names, internal file paths redacted to placeholders
  • Email and secrets redacted
  • Session ID, worktree hashes, commit prefixes, all timestamps preserved as load-bearing evidence

Happy 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).

SomeoneKong · 3 months ago

Thanks @bugbubug — the quantitative timing data (25 fires in 24 min,
~9s median interval, 15 forced 等。 no-op turns) and the
self-incriminating reason field excerpt are exactly the evidence I
couldn'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: false anyway is the strongest single argument for the proposed
idle short-circuit. The negative result on run_in_background + 600s
timeout 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:

  • #58297 is about the TUI agents view inferring agent status

from live background subprocesses (UI display logic, pgrep-based
inference).

  • #58381 is about the TUI agents view misclassifying

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 /goal evaluator'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.

rnanayakkara-infotrack · 3 months ago

+1
Currently /goal doesn't:

  • Consider any backgrounded task or agent as pending work. It keeps pinging the main agent.
  • Consider backgrounded tasks might be pre-requisites before proceeding

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" />

github-actions[bot] · 1 month ago

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.