[BUG] Failed auto-update download leaks multiple GB per process → concurrent native sessions OOM the host (macOS arm64, 2.1.165 fetching 2.1.168)
Preflight
- [x] searched existing issues, this one's distinct from #32701, #4953 and #14985 (details at the bottom)
- [x] single bug
- [x] on the latest version i can install (2.1.165). catch: 2.1.168 is the newest published build, but the bug is that the updater can't install it here, so 2.1.165 is as current as i can get.
What's Wrong?
on macOS arm64 with the native installer, the auto-updater keeps trying to go 2.1.165 -> 2.1.168 and the download never finishes. it leaves a 0-byte versions/2.1.168 and a pile of empty staging/2.1.168.* dirs. while it's doing that, the claude process eats memory and never releases it until killed. individual processes climbed to 4-20 GB (worst hit 19.7 GB ~24s after starting, ~800 MB/s).
i usually run tens of headless claude processes at once (10-30, subagent fan-out), fine on 2.1.165 for the last few days. each of the big memory hogs had its own staging dir, and together they hit ~40 GB, well past what this 8 GB machine can take. the kernel went to jetsam and killed ~30 daemons in one sweep, locking up the box until a hard restart. happened three times in one morning (07:37, 08:12, 08:45), each lined up with an update attempt.
probably the same leak as #32701 (async generator parked on a promise that never resolves, holding the downloaded response + arraybuffers). no heap dump from these runs so that's a guess, but #32701 was closed as a duplicate, was about a download that succeeded, and never reached the machine-wide OOM you get when a pile of native processes do this at once.
What Should Happen?
a failed/stalled download should release its buffers and give up cleanly (cap memory, back off, skip). it shouldn't add gigabytes to a session, normal ones sit at 0.7-1.2 GB. a botched update on one machine shouldn't be able to OOM the whole thing.
Error Messages/Logs
nothing's printed, it fails silently. evidence is in macOS's own logs.
(1) jetsam report from 08:45, top processes by memory. claude shows up under its version string (2.1.165), and each matches an empty staging dir with the same PID:
footprint pid matching update-staging dir (all empty)
--------- ----- ----------------------------------------
8145 MB 77795 ~/.cache/claude/staging/2.1.168.77795.*
7647 MB 78319 ~/.cache/claude/staging/2.1.168.78319.*
7228 MB 78626 ~/.cache/claude/staging/2.1.168.78626.*
6351 MB 78075 ~/.cache/claude/staging/2.1.168.78075.*
6215 MB 79044 ~/.cache/claude/staging/2.1.168.79044.*
5824 MB 79251 ~/.cache/claude/staging/2.1.168.79251.*
3891 MB 77451 ~/.cache/claude/staging/2.1.168.77451.*
588 MB 77200 ghostty (just the terminal, not the culprit)
system at kill: compressor 2267 MB, free 191 MB (8 GB RAM), so jetsam swept ~30 daemons
(2) the staging dirs. first number after 2.1.168. is the PID (the long one after is a timestamp), and those PIDs match the jetsam list exactly. all empty:
$ ls -ld ~/.cache/claude/staging/2.1.168.*
drwxr-xr-x 2 user staff 64 Jun 7 08:12 .../staging/2.1.168.77451.1780845139881/
drwxr-xr-x 2 user staff 64 Jun 7 08:16 .../staging/2.1.168.77795.1780845361953/
drwxr-xr-x 2 user staff 64 Jun 7 08:17 .../staging/2.1.168.78075.1780845420612/
drwxr-xr-x 2 user staff 64 Jun 7 08:17 .../staging/2.1.168.78319.1780845467540/
drwxr-xr-x 2 user staff 64 Jun 7 08:19 .../staging/2.1.168.78626.1780845582361/
drwxr-xr-x 2 user staff 64 Jun 7 08:20 .../staging/2.1.168.79044.1780845646505/
drwxr-xr-x 2 user staff 64 Jun 7 08:21 .../staging/2.1.168.79251.1780845663304/
(3) the 0-byte download it left behind:
$ ls -l ~/.local/share/claude/versions/2.1.168
-rw-r--r-- 1 user staff 0 Jun 7 07:15 .../versions/2.1.168
$ ls -l ~/.local/share/claude/versions/2.1.165
-rwxr-xr-x 1 user staff 219526944 Jun 4 23:01 .../versions/2.1.165 (current, works fine)
Steps to Reproduce
environment-dependent, you need the 2.1.168 download to fail, which it does every time here (staged file stays 0 bytes):
- macOS arm64, native install (
~/.local/bin/claude->~/.local/share/claude/versions/<ver>), on 2.1.165 while stable offers 2.1.168 - start a handful of claude processes (headless is easiest), leave them running
- each one's memory creeps into the GBs within seconds, with a matching
~/.cache/claude/staging/2.1.168.<pid>.*dir that stays empty - enough of them and the machine runs out of memory
to catch it before the OOM, run this while sessions are up:
ls -d ~/.cache/claude/staging/2.1.168.* 2>/dev/null # one dir per in-flight update attempt
ps -axo rss=,pid=,ucomm= | awk '$3=="2.1.165"{printf "%.1f GB pid=%s\n",$1/1048576,$2}' | sort -rn
the staging PIDs line up 1:1 with the claude processes using the most RAM.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.165. the binary didn't change, it ran 10-30 concurrent sessions fine Jun 4-6. it only started ballooning once 2.1.168 showed up as the offered update (~07:15 Jun 7), so the regression is in the update-to-2.1.168 path, not anything local.
Claude Code Version
2.1.165 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
setup: macOS 26.5 (build 25F71), arm64, 8 GB RAM. terminal Ghostty, shell zsh. native installer (~/.local/bin/claude -> ~/.local/share/claude/versions/2.1.165). on Opus, but the updater's model-independent.
worth noting: the memory's in the claude processes, which are children of the terminal's shells, so a process viewer can pin the blame on the terminal app. ghostty itself never went over ~1 GB.
root cause guess (inference, can't see the source): same as #32701, the download's response/arraybuffers held by an async generator stuck on a promise that never resolves. when the 2.1.168 fetch stalls nothing frees them, the updater retries, it piles up. the empty staging dirs (nothing written to disk) fit a download dying mid-stream with the buffers stuck in memory.
related issues (and why this is separate):
- #32701, same leak mechanism i think, but closed as a duplicate and about a download that succeeded. this is it live on 2.1.165 -> 2.1.168, triggered by a failing download, escalating to a machine-wide OOM.
- #4953 (open), big memory growth on linux over long sessions, but root cause's unattributed and there's no updater/staging angle, so probably separate.
- #14985, DISABLE_AUTOUPDATER=1 not always honored, which matters since that's the only workaround.
- #47253 / #65478, native updater vs concurrent sessions (deleting/renaming the running binary). this looks like the memory-exhaustion version of that.
workaround: export DISABLE_AUTOUPDATER=1 (or "env": {"DISABLE_AUTOUPDATER": "1"} in settings.json). per #14985 it's not always respected, so it's a band-aid, not a fix.
severity: rough for anyone running lots of concurrent native sessions (CI fan-out, agent orchestration) on machines without much RAM, since one failed update can quietly take down the box.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗