[BUG] Claude Code wastes wall-clock time polling for background tasks that already completed
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?
Bug report — Claude Code wastes wall-clock time polling for background tasks that already completed
Product: Claude Code (CLI)
Severity: Medium (correctness of behaviour, not safety — but burns user
time and cache context while stalling the conversation)
Model observed on: Opus 4.7 (1M context)
Date observed: 2026-04-21
Summary
When Claude Code launches background tasks via Bash withrun_in_background: true, the runtime reliably emits<task-notification> events with <status>completed</status> and the path
to the captured output file. These events are delivered to the model as
first-class signals that work is done.
Despite this, the model often writes and executes polling loops of the form:
until [ -f <task_id>.done ] && [ -f <other_task_id>.done ]; do sleep 15; done
…in order to "wait" for background tasks to finish. In practice this
re-implements, badly, a signal that the runtime is already providing for
free. Worse, the model sometimes stacks additional polling loops on top of
tasks that have already completed, causing the conversation to stall for
many minutes while it waits for a condition that was satisfied long ago.
I just observed a session where a user-facing "task taking a whole hour"
complaint was caused by exactly this pattern — four background builds
completed within ~minutes, but the polling-loop wrapper I wrote kept the
conversation effectively frozen for much longer before I read the results.
Expected behaviour
- The model should not write poll-the-filesystem wait loops. The
runtime already emits <task-notification status="completed"> events
exactly for this purpose. The correct action is: issue the background
calls, continue with other work or respond to the user, and respond to
the notification events when they arrive.
- The system prompt for Claude Code already contains guidance that says as
much ("If waiting for a background task you started with
run_in_background, you will be notified when it completes — do not
poll.") — but the model is clearly not adhering to it. This suggests the
training signal for the "don't poll" rule isn't landing strongly enough,
or the model is rediscovering a comforting-but-wrong polling pattern
under load.
Actual behaviour
- Model writes polling loops, burns wall-clock time, pays the cache-miss
cost of waiting past the 5-minute prompt-cache TTL, and responds late.
- In the worst case the model stacks polling loops across multiple turns
on tasks that already completed, compounding the delay.
Suggested fixes (pick any combination)
- Training / eval: add explicit negative examples to the "do not poll"
training data — show the model a transcript where four background jobs
complete, the notifications arrive, and the correct next action is to
read the captured output files directly, not to write an until loop.
- Runtime guardrail: reject or warn-on-use of
Bashcommands whose
bodies match until .* \.done .*; do sleep. Point the model at the
already-arrived notification events.
- Harness surfacing: after a background-task completion notification
is emitted, proactively surface a short "the output file is at X, read
it with Read" hint in the next turn's context to bias the model
toward the correct action.
- System prompt tightening: the existing line "do not poll" could be
escalated to an anti-pattern callout with a concrete example of the
failing behaviour — models seem to respond better to "don't write code
that looks like this" than to "don't poll".
Impact
- User-visible: conversation appears to hang for minutes after work is
actually done.
- Cost-visible: every minute past the 5-minute prompt-cache TTL pays the
full cache-miss price. Polling-loop waits reliably exceed that window.
- Trust-visible: users assume the model is actually working on something
during those dead minutes, and are surprised/angry when the work turns
out to have been done long ago.
Why this matters
The whole point of run_in_background: true is to let the model do
parallel work without blocking. The existence of polling-loop waits
undoes that benefit. It's not a subtle performance optimisation — it's
the difference between a 3-minute and a 60-minute user-perceived
conversation.
What Should Happen?
Clause should respect the user's time more.
Error Messages/Logs
Steps to Reproduce
- Launch multiple independent Bash commands with
run_in_background: true
(e.g. four parallel builds across a monorepo).
- As the model, write a subsequent Bash call whose body is an `until
[ -f .done ] && [ -f .done ] ...; do sleep N; done` loop, intending to
"wait" for all of them before reading their logs.
- Observe that the polling call holds the model turn open even when
<task-notification> events with <status>completed</status> arrive —
and sometimes even after all of them have arrived, because the inner
until condition is checking a filesystem sentinel that was never
actually produced by the polled task.
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.111
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
IntelliJ IDEA terminal
Additional Information
This bug report was authored by Claude Code based on the question asked "Why is this task taking so long?"
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗