[BUG] Blocked `sleep` recommends an unbounded `until`-loop, which becomes an unkillable background task
Preflight
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code
Related but distinct: #88702 (background timeout ignored) and #89625 (background tasks orphaned across sessions) describe what happens to the resulting process. This issue is about the guidance that produces it, which neither covers.
What's Wrong?
Foreground sleep is blocked by the Bash tool. The block message recommends an until-loop as the replacement:
Blocked:sleep 45followed by:cat .... To wait for a condition, use Monitor with an until-loop (e.g.until <check>; do sleep 2; done). To wait for a command you started, userun_in_background: true. Do not chain shorter sleeps to work around this block.
The recommended construct — until <check>; do sleep 2; done — is unbounded by construction. If <check> never becomes true, it never exits. The message offers no accompanying advice to cap iterations or attach a deadline, and the example itself has neither.
The agent is thus steered from a bounded wait (sleep N, which always terminates) to an unbounded one, and the failure mode is silent.
In my case this produced three shells of the form:
until <cmd> | grep -q "<string that never appeared>"; do sleep 4; done
each declared with an explicit foreground timeout. Each exceeded its timeout, was promoted to background (losing the bound — see #88702), and was still running five days later, across multiple session boundaries. They surfaced only when the desktop app refused to restart with an unactionable "there's a running task here" message. Their .output file mtimes confirmed they were live the entire period.
The full chain, where every step after the first follows the product's own instructions:
- Agent writes a bounded wait (
sleep N) → blocked. - Block message recommends an unbounded
until-loop. - Agent writes the unbounded loop.
- The condition never becomes true.
- Foreground timeout fires → promoted to background, discarding the bound.
- Loop runs indefinitely, outlives the session, blocks app restart days later.
What Should Happen?
The block message should recommend a bounded form, and its example should carry its own deadline. For example:
for i in $(seq 1 60); do <check> && break; sleep 2; done
or explicitly instruct that the loop must have an iteration cap or a timeout/gtimeout wrapper.
Ideally the tool would also refuse (or warn on) a while/until loop submitted with no in-command deadline, since that shape is unbounded regardless of which tool runs it.
Error Messages/Logs
# The block message (verbatim):
Blocked: sleep 45 followed by: cat /path/to/output tail -25. To wait for a
condition, use Monitor with an until-loop (e.g. `until <check>; do sleep 2; done`).
To wait for a command you started, use run_in_background: true.
Do not chain shorter sleeps to work around this block.
# What the recommendation produced (one of three, paths redacted):
until xcrun simctl spawn <udid> log show --last 15s \
--predicate 'process CONTAINS "<AppName>"' 2>/dev/null | grep -q "Running application"
do sleep 5; done
# Foreground call declared: timeout: 420000
# Result: "Command did not complete within its 420s timeout and was moved to the
# background (ID: <task-id>)."
# Still running 5 days later; killed manually via TaskStop.
Environment
- Claude Code 2.1.251 (desktop app)
- macOS 15.5, arm64
Additional Context
The proximate cause was the agent writing an unbounded loop, which is an agent error. But the product recommends that exact shape in the block message, with an example that has no bound. Fixing the recommendation text is a small change that removes the most common way this class of orphan gets created in the first place.