Background task respawns infinitely when command hangs, causing fork bomb

Resolved 💬 8 comments Opened Mar 22, 2026 by CaullenOmdahl Closed May 28, 2026

Bug Report

Claude Code version: 2.1.74
OS: Fedora 43 (Linux 6.19.8)
Node: v24.13.1

Description

When a background Bash task (run_in_background: true) hangs due to an external dependency failure, Claude Code respawns the command infinitely, creating a fork bomb. In our case this consumed 1,300+ bash processes, 41GB of 64GB RAM, and would have triggered the Linux OOM killer and required a hard reboot if not caught manually.

Steps to Reproduce

  1. Run a Bash tool call with run_in_background: true that pipes through grep/head and depends on an external connection:

``
adb shell "uiautomator dump /sdcard/ui.xml 2>&1 && cat /sdcard/ui.xml" 2>&1 | grep -oP '<node[^>]*text="(Pair|Cancel)"[^>]*>' | head -5
``

  1. The external connection (wireless ADB) drops mid-command
  2. The pipe chain breaks, the shell exits with an error
  3. Claude Code respawns the command — repeatedly and without limit

Observed Behavior

The spawned command template:

/bin/bash -c source ~/.claude/shell-snapshots/snapshot-bash-{timestamp}-{id}.sh \
  && shopt -u extglob 2>/dev/null || true \
  && eval "{escaped_command}" \
  && pwd -P >| /tmp/claude-{session_id}-cwd

What happens on failure:

  • The ADB connection drops → adb shell hangs or errors → pipe breaks → shell exits
  • pwd -P >| /tmp/claude-*-cwd never executes (command failed before reaching it)
  • Claude Code's Node.js process detects the failure and respawns the command
  • Each respawn sources a ~247KB shell snapshot (full environment: nvm, systemd, bash functions)
  • New processes immediately fail the same way → respawn loop

Memory per process (measured):

  • Virtual: 228MB (VmSize)
  • Resident: 5.4MB (VmRSS) — grows with ADB client + pipe buffers
  • At 1,300 processes: ~7GB RSS minimum, ~290GB virtual reservation

Timeline of escalation:

  1. Initial state: ~9GB RAM used, 650 processes
  2. Within minutes: 2,000+ processes, 41GB RAM, load average 58
  3. sudo pkill -9 -f "snapshot-bash" — kills existing processes, new ones immediately respawn
  4. Deleting/overwriting the snapshot .sh file — processes still respawn
  5. If unchecked: fills all 64GB RAM + 8GB swap → Linux OOM killer → system freeze → hard reboot required

No user-accessible way to stop it:
| Recovery attempt | Result |
|---|---|
| Reject the tool call in UI | Does NOT stop the background task |
| /clear | Does NOT stop the background task |
| TaskStop tool | "No task found" — Bash background tasks aren't registered as stoppable tasks |
| sudo pkill -9 -f "snapshot-bash" | Kills processes, but Claude Code immediately respawns them |
| Delete/overwrite snapshot .sh file | Processes still respawn (Node.js re-creates them) |
| kill -9 <claude_pid> | Only working fix — kills the parent Node.js process |

Root Cause Analysis

We traced through the minified cli.js source:

  1. Background Bash commands are spawned via Node.js child_process.spawn() with detached: true and stdio redirected to a task output file
  2. The process lifecycle is managed by class cK8, which listens for exit and error events
  3. The cK8 class itself does not contain retry logic — it resolves its promise on exit
  4. However, something upstream in the tool execution pipeline re-triggers the spawn when the process exits with a failure code. The exact respawn mechanism is unclear from the minified source, but the behavior is deterministic: every pkill is followed by immediate respawn of the same command

The shell snapshot (snapshot-bash-*.sh) is a 247KB file containing base64-encoded bash functions (nvm completions, systemd OSC sequences, etc.) that gets sourced on every spawn, adding overhead to each fork.

Expected Behavior

  1. Retry limit: Background tasks should cap retries (e.g., max 3) before giving up
  2. Tool rejection = task cancellation: Rejecting a tool call should cancel any associated background process
  3. /clear should kill background tasks: Or at minimum, provide /kill-background command
  4. TaskStop should work for Bash tasks: Background Bash tasks should be registered in the task system
  5. Graceful degradation: If a background task fails repeatedly, notify the user instead of silently respawning

Workarounds

Until this is fixed, users can protect themselves:

Option 1: Environment variable (disables all background tasks globally)

# Add to ~/.bashrc or ~/.zshrc
export CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1

This removes the run_in_background parameter from the Bash tool schema entirely — Claude cannot use it.

Option 2: PreToolUse hook (blocks background Bash in specific projects)

Add to .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"$CLAUDE_TOOL_INPUT\" | grep -q '\"run_in_background\"\\s*:\\s*true' && echo 'BLOCK: Background bash tasks disabled — fork bomb risk.' >&2 && exit 2 || exit 0"
          }
        ]
      }
    ]
  }
}

If you're already in the fork bomb state:

# Find and kill the Claude Code process (the Node.js parent)
ps aux | grep claude | grep -v grep
kill -9 <claude_pid>

# Then clean up any remaining orphaned processes
sudo pkill -9 -f "snapshot-bash"

Impact

Severity: Critical — This is a silent, unrecoverable resource exhaustion bug. The user gets no warning. The only visible symptom is the system slowing down. By the time they notice, thousands of processes may already be running. On systems without swap or with smaller RAM, this would trigger OOM and kill unrelated processes (browsers, IDEs, unsaved work).

View original on GitHub ↗

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