anthropics / anthropics/claude-code
Three wedged claude processes leak 92 GB and freeze macOS: startup failure path loops allocating instead of exiting (2.1.234)
- 主要言語
- Python
- スター
- 145k
- フォーク
- 23.1k
- PR マージ指標
- PR 指標を取得中
説明
## 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):
```
??? (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](https://github.com/user-attachments/files/31305113/claude-code-93gb-leak-evidence.zip)
コントリビューションガイド
このリポジトリのコントリビューションガイドは索引されていません
調査の方向性
Start with the attached README.md and compare the WindowServer spindump, JetsamEvent files, and daemon-log-excerpt.txt. Trace the Workflow fan-out's helper startup and readiness-retry path, focusing on the pre-update child processes described in the evidence. Done means the startup failure is reproduced or explained and failed helpers cannot continue unbounded allocation or remain abandoned.
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- bun, macos
- 領域
- cli, devtools, operating-systems
- issue の種類
- バグ
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 活発
- 明瞭さ
- 説明が足りない
- 初心者へのやさしさ
- 30/100