[BUG] 100% CPU freeze on TaskStop — runaway posix_spawn loop in Bun runtime

Resolved 💬 4 comments Opened Feb 21, 2026 by boshu2 Closed Feb 25, 2026

Bug Description

Claude Code enters an infinite CPU loop (100% single core) when a background task is stopped via TaskStop (the internal tool invoked when canceling/stopping a background Bash command). The process becomes completely unresponsive — no input accepted, no API calls, no progress. The system becomes starved for CPU: other processes cannot fork, the terminal is unusable, and the only remedy is kill -9 from a pre-existing shell.

This is 100% reproducible and has persisted across every version for the past month (at minimum v2.1.9 through v2.1.50).

Environment

| Component | Value |
|-----------|-------|
| Claude Code Version | 2.1.50 (reproduced since ~2.1.9) |
| Platform | macOS 26.2 (Darwin 25.2.0) |
| Architecture | ARM64 (Apple Silicon M4 Pro) |
| CPU | 12 cores |
| RAM | 32 GB |
| Shell | zsh |
| Terminal | tmux inside iTerm2 |

Steps to Reproduce

  1. Start a Claude Code session
  2. Ask Claude to run any Bash command with run_in_background: true

(e.g., sleep 60, npm test, any long-running command)

  1. Ask Claude to stop the background task — this triggers the internal TaskStop tool
  2. Immediately: Claude Code process pegs 100% CPU on one core
  3. Terminal is unresponsive — cannot type, no output
  4. System becomes sluggish — other terminals lag, new process spawning fails
  5. Only remedy: kill -9 <pid> from a pre-existing terminal session

The trigger is specifically TaskStop. Running background tasks alone is fine. The freeze occurs at the moment the stop/cancel is requested.

Symptoms

  • 100% CPU on single core — main thread spinning, never yields to event loop
  • Complete UI freeze — no response to any input
  • System-wide starvation — cannot fork new processes (macOS becomes unresponsive)
  • Cannot interrupt — Ctrl+C does nothing, only kill -9 works
  • Network connections remain open (server-side appears fine)
  • Work in progress is lost — must force-kill and lose session state

Profiling Evidence (attached)

CPU Timeline captured with 1s-resolution ps polling during live reproduction:

13:02:37-43  →  12-22% CPU, state S+ (normal, processing API)
13:02:44     →  45% CPU                (TaskStop invoked)
13:02:45     →  59% CPU                (spike begins)
13:02:50     →  80% CPU, state R+      (runaway loop)
13:02:51-58  →  41-66% CPU, state U+   (UNINTERRUPTIBLE)
13:02:58     →  "fork: Resource temporarily unavailable" ← SYSTEM STARVED

sample call-stack profiler (33,579 samples on main thread):

The main thread (100% of samples) is stuck in a tight posix_spawn loop:

Thread_29909942  com.apple.main-thread  (33579 samples, 100%)
 └─ 22694 samples (67.6%): spawn loop
     └─ socketpair()    → 423 samples
     └─ fcntl()         → 160 samples (setting FD flags)
     └─ setsockopt()    → 137 samples
     └─ posix_spawn()   → 614 samples (TRYING TO SPAWN PROCESSES)
     └─ posix_spawn_file_actions_adddup2() → 21 samples

The Bun runtime's process spawning machinery enters an infinite retry loop — repeatedly calling socketpairfcntlsetsockoptposix_spawn without yielding to the event loop.

System impact confirmed: load jumped from 4.39 to 5.90 in 21 seconds, then the diagnostic script itself got "fork: Resource temporarily unavailable" — the process exhausted the system's ability to fork.

FD snapshot: Only CHR (tty) file descriptors visible on the claude process. No PIPEs. This suggests the pipes from the killed background task were cleaned up, but the spawn-loop continued anyway — possibly a retry loop that doesn't check exit conditions.

Root Cause Hypothesis

Based on the profiling evidence:

  1. TaskStop sends a signal to kill the background child process
  2. The Bun runtime's process manager enters a respawn/reconnect loop — attempting to re-establish communication with the dead child via socketpair + posix_spawn
  3. Each spawn attempt fails or completes but finds no child, so it immediately retries
  4. The loop has no backoff, no max-retry, no yield — it hammers syscalls at full CPU speed
  5. This starves the event loop, the UI thread, and eventually the entire system

This is NOT a "busy-wait on dead pipe" (no PIPEs visible). It's a runaway respawn loop in the Bun process management layer.

Diagnostic Bundle

Attached: claude-freeze-diag.tar.gz (666KB) containing:

| File | Purpose |
|------|---------|
| 01-sample.txt | KEY EVIDENCE — macOS CPU profiler call stacks (3.4MB, 33K samples) |
| 03-cpu-timeline.csv | CPU% at 1s resolution showing spike onset |
| 05-fd-pipes.txt | File descriptor snapshots (5s intervals) |
| 06-load.txt | System load showing starvation |
| 00-baseline.txt | Environment + process state before trigger |

Related Issues

| Issue | Title | Relevance |
|-------|-------|-----------|
| #23590 | Infinite CPU loop on --resume | Same symptom (main thread loop), different trigger |
| #18532 | v2.1.9 Complete Freeze - 100% CPU | Broader freeze report, 23 comments, links to 7+ related issues |
| #4580 | 100% CPU during multi-agent task JSON serialization | Event loop wedge during task operations |
| #6474 | Unresponsive hang with 120% CPU usage | Similar system starvation pattern |

Severity

High. This bug makes run_in_background + stop/cancel completely unusable. Heavy Claude Code users who rely on background tasks (CI runners, test watchers, parallel builds) hit this multiple times per day. Each occurrence requires force-killing the session, losing all in-progress work and context.

Suggested Fix Direction

In the Bun process spawning layer used by TaskStop:

  1. Add max retry count and exponential backoff to the process spawn loop
  2. After killing a child process, do not attempt to respawn/reconnect — just clean up
  3. Ensure the spawn loop yields to the event loop between attempts (e.g., await sleep(0))
  4. Add a circuit breaker: if spawn fails N times in M seconds, stop trying and report an error

View original on GitHub ↗

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