`claude -p` does not exit after final text when session used `run_in_background=True` Bash tools, even after those processes complete naturally
Summary
In claude -p (one-shot non-interactive) mode, the CLI main loop does not cleanly exit after the final assistant text when the session has used run_in_background=True on Bash tool calls — even when the underlying OS processes have exited naturally and the harness-tracked task outputs have been consumed.
The session emits no further JSONL writes, no hook activity, and no filesystem writes during the hang window. It has to be SIGKILLed by an external timeout.
CLI version observed: 2.1.148.
Repro
claude -p --dangerously-skip-permissions --verbose <<'PROMPT'
Run these two shell commands in parallel using run_in_background=True:
- sleep 5
- sleep 7
Wait until both complete (poll with pgrep or jobs). Then write a one-line file at /tmp/done.txt that says "done". Then say "all clear" and exit.
PROMPT
Expected: CLI exits within ~10s of starting.
Actual: CLI emits "all clear", writes /tmp/done.txt, then hangs indefinitely. Has to be SIGKILLed.
Production evidence
This was originally diagnosed from a real overnight task that hit a 45-minute SIGKILL ceiling. Key signal:
- Final assistant text emitted at T+10 minutes (real compute).
- Process SIGKILLed by external runner at T+45 minutes (34:21 of pure idle hang).
- During the hang window: 0 hook activity, 0 JSONL writes, 0 filesystem writes.
- The session had 2
run_in_background=TrueBash tool calls earlier (npm test,pytest), both polled to natural completion via apgrep-based wait loop, with outputs tailed from the/tmp/claude-1000/.../tasks/task directories before the final report-write. - Statistical signal: of 25 non-interactive task transcripts the same night, only the one that used
run_in_background=Truehit the 45-min SIGKILL. The other 24 (all without backgrounded Bash) exited cleanly.
Full transcript is available on request — not pasted inline because it contains internal paths and identifiers.
Hypothesis
In -p mode the CLI main loop appears to wait for harness-tracked background-task entries to be explicitly released (via TaskStop or session-end cleanup) before exiting, but does NOT auto-release entries when the underlying processes exit naturally.
Workarounds (for users hitting this today)
- Avoid
run_in_background=Truein-pmode entirely. - Or: explicitly call
TaskStopon each backgrounded entry before the final assistant text. - Or: wrap the
claude -pinvocation in an external timeout so the hang is bounded.
Environment
- Claude Code CLI: 2.1.148
- OS: Ubuntu 24.04 LTS, kernel 6.8.0
- Invocation:
claude -p --dangerously-skip-permissions --verbose <<'PROMPT' ... PROMPT - Runner: 45-minute
timeoutwrappingclaude -p
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗