Background Bash processes can run indefinitely with no visibility to user or model
Summary
Bash invocations started with run_in_background: true have no upper-bound duration and no built-in way for the user or the model to see what's currently running. A faulty exit condition in a polling loop can leave a background process running for hours undetected. The status bar shows that something is running but doesn't expose what it is or how long it's been alive.
Repro
In an interactive Claude Code session, I asked Claude to push a branch and wait for the pre-push integration suite to finish. Claude (Opus 4.7) ran:
git push -u origin <branch> # run_in_background: true → task id A
Then, redundantly, started a polling loop to wait for it:
until grep -E "(Passed|Failed|done|Everything up-to-date|To github\.com)" \
/tmp/.../tasks/A.output | tail -1 | grep -q "github.com\|done\|Everything"; do
sleep 5
done; cat /tmp/.../tasks/A.output # run_in_background: true → task id B
The push completed within ~1 minute. The loop's exit predicate matched only the last line of the push output — which was the branch set up to track message, not the github.com URL line — so the loop never exited. Task B kept polling every 5 seconds for several hours until the user noticed an unexplained shell in their status bar.
Expected
One or more of:
- The status bar (or a
/jobs-style command) exposes the list of currently-running background Bash tasks with command and elapsed time. - Background Bash tasks have an opt-in
max_durationparameter that auto-terminates if exceeded. - The harness emits a system-reminder to the model when a background task exceeds some threshold (e.g., 10 min), so the model can investigate or kill it.
- The harness emits a user-visible warning for the same.
Actual
- No user-facing affordance to see what's running. User had to ask the assistant, who then had to
ps -ef | grep ...to find the offending PID. - No notification to the model after the foreground push completed that a redundant background poller was still alive.
- The task stayed alive for ~several hours of wall time before the user investigated.
Why this matters
- Resource waste (process and shell snapshot per task).
- User confusion ("what's that shell my status bar shows running?").
- Model drift: the assistant said it had "learned a lesson" about this, which is hollow — the model is stateless across sessions. The harness is the durable layer; if it doesn't guard against this, the same class of bug recurs every session.
- Headless mode never completes: this problem has caused Claude Code running in headless mode to hang and never exit. A headless invocation that finishes its real work but leaves a background polling loop alive will sit there until the orchestrator times it out (or never, if there's no orchestrator timeout) — making Claude Code unusable in CI, scripted pipelines, and any automation that expects the process to exit cleanly when work is done.
Suggested fix
Cheapest viable surface: add max_duration_seconds to the Bash tool's run_in_background parameter (default None = no cap, opt-in cap). Pair it with a /jobs command users can run to inspect active background tasks. In headless mode, also consider auto-terminating background tasks at session-end rather than blocking on them.
Environment
- Claude Code version: 2.1.146
- Model: claude-opus-4-7 (1M context)
- Platform: Linux
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗