Three wedged claude processes leak 92 GB and freeze macOS: startup failure path loops allocating instead of exiting (2.1.234)

Status Open
Reported on v2.1.234
Maintainer reply None cached
Activity 0 comments · opened Aug 21, 2026

Environment

  • Claude Code 2.1.234 (native install, ~/.local/share/claude/versions/*, launched via the ~/.local/bin/claude symlink)
  • macOS 15.6.1 (24G90), MacBook Pro 18,1 (M1 Pro, 10 cores), 16 GB RAM
  • The session that triggered it was a long-running background session (remote control, background job template bg), hosted by a claude process that had been alive ~35 hours
  • The machine had been up 20 days; the auto-updater had installed 2.1.235 and then 2.1.238 while that host kept running on 2.1.234

Summary

During a Workflow tool fan-out, the session host made three attempts, ~2 minutes apart, to spawn a helper claude process. All three children wedged during startup before the JS runtime came up (2 threads each: main + mi-scavenger; every healthy claude process on the machine had 20–32). Each wedged child spun at ~15% of one core in an early-bootstrap loop doing read(2) calls into APFS and allocating anonymous memory at ~2.5–4 MB/s, never freeing, never exiting. Over 2.5 hours they grew to 92 GB combined on a 16 GB machine. Nothing killed them: they sat at jetsam priority 180, so jetsam killed 3,400+ other processes around them (in one event alone) while they kept growing. WindowServer starved, its watchdog fired, the UI froze, and the machine needed a force power-off. No kernel panic; pure memory exhaustion.

The workflow itself was unaffected: after the third failed spawn the attempts stopped, all 9 subagents ran to completion in the host process, and the workflow returned normally. I saw nothing until the machine froze hours later.

I have a full evidence bundle (WindowServer spindump that captured all three mid-leak, 13 JetsamEvent reports spanning the growth, daemon log) attached.

Impact

  • Complete system freeze requiring force power-off (boot record: Boot faults: btn_rst,finger_reset force_off)
  • Jetsam killed thousands of system daemons before the freeze (online-auth-agent ×271, SidecarRelay ×239, feedbackd ×201, biometrickitd ×196 in the 05:10 event, which lists 4,000 processes, 3,404 with reason vm-compressor-space-shortage)
  • The leaking processes were never touched by jetsam and could not be, at priority 180

Timeline (local time, UTC+3, 2026-08-21)

| Time | Event | Source |
|---|---|---|
| 04:07:04 | Session calls the Workflow tool (2 phases: 3 parallel research subagents, then 6 verify subagents) | session transcript |
| 04:07:06 | The 3 phase-1 subagents start writing transcripts (from the healthy host) | subagent transcripts |
| 04:08:41 | Wedged child 1 forks (pid 19622) | spindump Time Since Fork |
| 04:10:42 | Wedged child 2 forks (pid 26618), +121 s | spindump |
| 04:12:47 | Wedged child 3 forks (pid 33919), +125 s — then no further attempts | spindump |
| 04:10–04:22 | All 9 subagents complete normally; workflow returns its result | transcripts, journal |
| 04:33 | Last conversation activity; session idles awaiting user input | transcript |
| 04:55:05 | First JetsamEvent: the three hold 33.0 GB combined | JetsamEvent |
| 05:09:36 | WindowServer watchdog: main thread 40 s without checkin → UI freeze; spindump taken | .spin report |
| 05:10:00 | JetsamEvent lists 4,000 processes; 3,404 kills with vm-compressor-space-shortage | JetsamEvent |
| 06:50:37 | Last JetsamEvent before full stall: three hold 92.3 GB combined | JetsamEvent |
| 14:17 | Force power-off and reboot | ResetCounter, last reboot |

The daemon supervisor idle-exited at 06:51 local (03:51:05Z ... shutting down (cause=idle_exit, ... live_workers=0)) while the three were alive and growing — they were untracked orphans. The daemon log records no spawn for them.

The three processes

All three are identical in signature. From the spindump (05:09:36, mid-leak):

Process:          2.1.234 [26618]
Path:             /Users/USER/*/2.1.233
Codesigning ID:   com.anthropic.claude-code
Team ID:          Q6L2SF6YDW
Architecture:     arm64
Footprint:        14.31 GB
Pageins:          4 pages
Time Since Fork:  3534s
Num threads:      2
  • 2 threads: the main thread plus mi-scavenger (mimalloc). No Heap Helper Thread, no Bun Pool *, no tokio-rt-worker, no HTTP Client — all of which every healthy claude process on the machine had (20–32 threads). The JS runtime never finished initializing.
  • Pageins: 4 pages against a 14.31 GB footprint: the growth is anonymous memory, not file mappings.
  • Zero output: they wrote no transcript, log, or state file. The only files modified anywhere under ~/.claude in the fork window belong to my own workload.
  • Process identity: name 2.1.234, executable path versions/2.1.233 — the same name/path split as their parent, the 35-hour-old session host (pid 24239, forked at the moment 2.1.235 was installed). The children were spawned from the parent's own, by-then-twice-superseded binary. The Mach-O UUID (4C4C44B8-5555-3144-A159-0B2F2AC38AFE) matches the on-disk versions/2.1.234 file; note 2.1.235 and 2.1.238 share a different LC_UUID with each other, so LC_UUID appears to identify the Bun shell build rather than the release.

Main-thread stack, consistent across all 12 spindump samples and all three processes (a shallow loop; JS/JIT frames not symbolicated):

<truncated backtrace>
  ??? (2.1.233 + 346736) [0x102974a70]
    ??? (2.1.233 + 4240)   [0x102921090]
      ??? (2.1.233 + 19648 / + 19680)      ← syscall wrappers
        kernel: VFS read path → apfs (cluster_read / decmpfs frames)

One sample carries full provenance from process start:

dyld + 27544 → (2.1.233 + 11747816) → (+11746948) → (+316560) → (+346736) → (+4240) → (+19680)

So: a loop that reads files via read(2) and allocates without bound, at ~15–17% of one core (jetsam lifetime cpuTime: 554 s over 3,558 s alive at 05:10; 1,657 s over 9,502 s at 06:49).

Growth series (jetsam rpages × 16 KB, GB)

| Local time | pid 19622 | pid 26618 | pid 33919 | Combined | mem_regions (26618) |
|---|---|---|---|---|---|
| 04:55:05 | 10.5 | 12.5 | 10.0 | 33.0 | — |
| 05:10:00 | 11.3 | 13.8 | 11.1 | 36.2 | 11,322 |
| 06:49:02 | 25.5 | 36.5 | 29.7 | 91.7 | 28,465 |
| 06:50:37 | 25.6 | 36.8 | 29.9 | 92.3 | — |

Steady ~2.5–4 MB/s per process. mem_regions grows in proportion (~1.3 MB per region), i.e. thousands of separate anonymous mappings, and the kernel's largest zone at 05:10 was VM map entries at 224 MB — the leak was also inflating kernel memory. Per-process lifetimeMax equals current rpages in every snapshot: monotonic growth, nothing ever released.

What spawned them

Established:

  • Fork times are 95 s after the workflow's phase-1 subagents started, then +121 s, then +125 s. That is a readiness-timeout-and-retry cadence, not a parallel fan-out.
  • The spawner is the session host family (children carry its exact binary identity, including the stale-path oddity).
  • The daemon supervisor was alive and responsive through the window and logged no spawn for them; its own spare-host spawns (which it does log) exec the current 2.1.238 binary.
  • After 3 attempts the spawning stopped, and phase 2 (6 more subagents) spawned no processes. The subagent work itself ran in the host and completed.

So the host tried three times to stand up some auxiliary worker/helper process for the workflow, each child failed to initialize, and whatever code path handles that failure loops on file reads and allocation instead of exiting.

Suspected contributing condition

The host predated two auto-updates. It was exec'd from versions/2.1.233 ~35 h earlier, and by the time it spawned children, the installation had moved to 2.1.238 (version-named lock file locks/2.1.238.lock; old version files pruned — the versions/ directory was modified at 05:10, during the incident). A child spawned from a superseded binary into a post-update installation appears to hit a deterministic bootstrap failure (3 for 3), and the failure path leaks instead of exiting.

Two independent defects compound here:

  1. The leak: a startup/bootstrap failure path that loops read + allocate without bound instead of failing fast.
  2. No containment: the spawner abandoned children it timed out on (no kill, no reap), the daemon didn't track them, and at jetsam priority 180 macOS could not reclaim them either — it killed everything else first. Any one of a failed-child kill, an orphan reaper, a self memory limit, or a lower jetsam band for non-interactive helpers would have made this a non-event.

Relation to existing issues

Same observable class as #4953 (growth to 120+ GB, OOM-killed, open), #11315 (129 GB, system freeze), #22188 (93 GB, closed as duplicate of #4953): unbounded anonymous memory growth in claude processes. But this report is a distinct variant with different mechanics — the growth is in spawned children that never completed startup and never did any work, not in a working session's heap — and it comes with stack samples, thread signatures, fork timing, and a growth series, which those issues lack. The 2.1.238 changelog entry "Fixed unbounded memory growth in long interactive sessions: subagent tool results are now released once they leave the recent display window" addresses the in-session variant, not this one.

Open questions for maintainers

  • What does the host spawn ~95 s into a Workflow fan-out with a ~2-minute readiness timeout and 3 retries? That component's bootstrap-failure path is where the loop lives.
  • What does early bootstrap read repeatedly from disk while accumulating anonymous memory? (Candidates visible from outside: the ~300 MB self-binary, version/lock state, config/skill scans.)
  • Is the failure reachable on a current version, or only from a pre-update holdover host spawning children from a superseded binary? Long-lived background/remote-control sessions make that window days wide in practice.

Attachments

claude-code-93gb-leak-evidence.zip (added as a comment below, since CLI-filed issues cannot carry attachments):

  • WindowServer_*.userspace_watchdog_timeout.spin — spindump capturing all three processes mid-leak (stacks, threads, footprints, fork ages)
  • JetsamEvent-*.ips ×13 — the growth series 04:55–06:50 with per-process rpages, cpuTime, mem_regions, priority, plus the system-wide kill storm
  • ResetCounter-*.diag — the force power-off record
  • daemon-log-excerpt.txt — daemon supervisor log for Aug 19–21 (username redacted)
  • README.md — index and how each number above was derived

claude-code-93gb-leak-evidence.zip

View original on GitHub ↗