Bash run_in_background polling loop completes without firing task-notification
Summary
In a single session I twice hit a case where a run_in_background: true Bash command running a polling until loop transitioned from running to completed but no <task-notification> arrived in the conversation. The output file was created but stayed 0 bytes. Each time I only noticed the command was done many minutes later — once after ~50 minutes (when the user asked "status") and once after ~17 minutes (when the user asked "2 minutes how long ago?"). In both cases the underlying work (a GitHub Actions CI run) had completed within ~2 minutes.
Repro pattern
The exact background command in both cases was a CI-readiness poller of this shape:
until \
state=$(gh pr checks $PR --repo OWNER/REPO --json bucket,name 2>/dev/null) && \
[ -n "$state" ] && \
[ "$(echo "$state" | jq 'length')" -gt 0 ] && \
echo "$state" | jq -e 'map(.bucket \!= "pending") | all' >/dev/null; \
do sleep 30; done; \
echo "PR#$PR terminal:" && echo "$state" | jq -r '.[] | "\(.name): \(.bucket)"'
What I observed each time:
- Background task starts; harness returns the task id and output file path.
gh pr checksinitially returns an empty array (the workflow hasn't been registered yet); the until guard[ length -gt 0 ]is false; loop enterssleep 30.- By the time the sleep returns, the workflow has actually completed.
- The until loop's condition goes true; the bash process exits 0.
- Expected: a
<task-notification>message with the finalecholines as content. - Actual: no notification at all; output file remains 0 bytes; conversation continues as if the background task is still pending.
Workaround the model adopted
The user noticed both times — "status" or a time-callout in their next turn. I then ran gh again synchronously, discovered the CI had finished \~17 / \~50 min earlier, and merged manually. So the user pays the cost of remembering to ask.
Hypothesis
The two cases share a profile: a polling loop where the FIRST inner command returns an empty/zero-length result, the loop goes to sleep 30, then immediately after the sleep returns the inner command succeeds and the script exits within a few milliseconds of the final echo. The notification may depend on stdout-drain timing that this exit pattern misses.
A trivial-shaped repro to try:
sleep 30; true | true ; echo "done"
launched with run_in_background: true — expected to deliver a task-notification with "done" in the summary about 30 seconds later; if no notification arrives, that reproduces the symptom.
Impact
Multi-step orchestration (CI/CD watching, etc.) loses its main async signal. The user has to actively probe. It happens silently — the model has no way to tell that the harness dropped a notification, so it just keeps saying "standing by".
Environment
Claude Code session running Claude Opus 4.7. Linux sandbox host. Background bash via the standard run_in_background: true Bash tool surface. Issue happened twice in the same session, with two different background task ids (b97d2yl9o, b7qjjsf25) and two different PRs on different OXBO-Energy repos; both were polling gh pr checks ... --json bucket,name in the same shape.
What would help
- Either: a delivery guarantee — every
status: completedtransition for a background task should generate a task-notification, regardless of stdout-drain timing. - Or: a separate signal the model can read to tell that a background task's status is stale (e.g. a "this task completed N seconds ago but no notification was scheduled" hint when the model re-reads the output file).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗