Bash run_in_background polling loop completes without firing task-notification

Resolved 💬 1 comment Opened Jun 8, 2026 by ramonchis Closed Jul 15, 2026

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:

  1. Background task starts; harness returns the task id and output file path.
  2. gh pr checks initially returns an empty array (the workflow hasn't been registered yet); the until guard [ length -gt 0 ] is false; loop enters sleep 30.
  3. By the time the sleep returns, the workflow has actually completed.
  4. The until loop's condition goes true; the bash process exits 0.
  5. Expected: a <task-notification> message with the final echo lines as content.
  6. 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: completed transition 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).

View original on GitHub ↗

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