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

Resolved 💬 6 comments Opened May 13, 2026 by SomeoneKong 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 ↗

This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗