[BUG] Bash auto-background-on-timeout eligibility is evaluated post-rewrite and undocumented — hook-rewritten long commands are killed at 2m instead of backgrounded

Status Fixed / completed
Reported on v2.1.251
Maintainer reply None cached
Activity 0 comments · opened Aug 30, 2026 · closed Aug 31, 2026

Summary

Bash auto-background-on-timeout (a foreground command that outlives its timeout is converted to a background task instead of killed) is gated on a canAutoBackground predicate over the post-rewrite command shape, and the eligibility rules are undocumented. Consequences:

  1. A PreToolUse hook that rewrites a command (e.g. appending ; exit "${PIPESTATUS[0]}" to un-mask a pipeline's exit code) silently strips auto-background eligibility: the identical workload that would have been converted at 120s and completed is instead SIGTERMed (Command timed out after 2m 0s, exit 143).
  2. The model has no way to know which command shapes are eligible, so it cannot predict whether a long foreground command will survive the default timeout (converted) or die (killed).

Environment

  • Claude Code 2.1.251, Windows 11 (win32 x64), Git Bash, main agent (not a subagent)

Repro (no hook required)

A/B, both foreground, both without an explicit timeout:

  • A: python -c "import time; time.sleep(150); print('done')" 2>&1 | tail -2

→ at 120s: "Command did not complete within its 120s timeout and was moved to the background (ID: …)" — completes with exit 0.

  • B: python -c "import time; time.sleep(150); print('done')" 2>&1 | tail -2; exit "${PIPESTATUS[0]}"

→ at 120s: Command timed out after 2m 0s, exit 143. Killed.

The only difference is the trailing ; exit … statement — a plain pipeline qualifies as auto-backgroundable, a two-statement command does not. With a PreToolUse hook doing the append via updatedInput, case A silently becomes case B without the model or user seeing the rewritten shape's consequence.

Expected

Any of:

  • evaluate canAutoBackground on the original (pre-rewrite) input, since the rewrite was not the model's choice; or
  • document the eligibility rules (simple/pipeline shapes qualify; multi-statement ; chains, compound && scripts, loops do not; main-agent only) so hook authors and models can reason about the 120s cliff; or
  • when an ineligible command is killed at the default timeout, note in the error that auto-backgrounding was skipped and why.

Impact

Long test suites and builds behind rc-instrumentation hooks die at exactly 2 minutes and look like flaky infrastructure. Related: #89495 (auto-background in -p mode), #79321 (updatedInput on Bash), #83353 (rewrite race).

🤖 Generated with Claude Code

View original on GitHub ↗