`claude -p` does not exit after final text when session used `run_in_background=True` Bash tools, even after those processes complete naturally

Resolved 💬 4 comments Opened May 22, 2026 by Crossthreaded Closed Jun 21, 2026

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=True Bash tool calls earlier (npm test, pytest), both polled to natural completion via a pgrep-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=True hit 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=True in -p mode entirely.
  • Or: explicitly call TaskStop on each backgrounded entry before the final assistant text.
  • Or: wrap the claude -p invocation 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 timeout wrapping claude -p

View original on GitHub ↗

This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗