[BUG] Claude Code wastes wall-clock time polling for background tasks that already completed

Resolved 💬 1 comment Opened Apr 21, 2026 by AdmiralPotatoAtAroundAr Closed May 27, 2026

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

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

  1. Runtime guardrail: reject or warn-on-use of Bash commands whose

bodies match until .* \.done .*; do sleep. Point the model at the
already-arrived notification events.

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

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

  1. Launch multiple independent Bash commands with run_in_background: true

(e.g. four parallel builds across a monorepo).

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

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

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗