[BUG] Long-running sessions enter permanent V8 GC thrash (RSS sawtooth 2.4↔5.9 GB, 300–400% CPU); TUI stops reading the keyboard and SIGTERM is ignored — only kill -9 works
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
After several hours of normal use, a claude session process stops doing useful work and enters what looks like a permanent V8 garbage-collection loop: RSS oscillates by gigabytes every few seconds while CPU sits at 300–400% (3–4 cores).
The session becomes completely unreachable:
- The TUI registers no keyboard input at all — typed characters do not echo into the prompt, so
/exitcannot even be entered. - SIGTERM is ignored.
kill <pid>has no effect; onlykill -9removes the process.
Three independent input paths are dead at once — terminal keystrokes, slash commands and POSIX signals — while the process keeps consuming 3–4 cores. That points at the event loop being starved by GC rather than at any single stuck handler.
Four separate occurrences within ~9 hours on one machine, across three projects and two Claude Code versions (2.1.278 and 2.1.280). Each pushed the machine into heavy swap (up to 9.2 GB of 10.2 GB) with sustained thermal load, until the process was killed.
Measurements
CPU measured as process CPU-time delta over a 10 s wall-clock interval (ps -o time=), so 10 s per 10 s = one core fully busy.
| # | Time | Project | Age | RSS | CPU per 10 s wall | Session file last written | History |
|---|------|---------|-----|-----|-------------------|---------------------------|---------|
| 1 | Sep 23, 08:04 | rz | 14 h 37 m | 6.2 GB (38.5% of RAM) | 40 s (≈4 cores) | 2 h 52 m earlier | 32 MB |
| 2 | Sep 23, 13:26 | wallet | 5 h 12 m | 4.7 GB (41%) | 40 s (≈4 cores) | 3 min earlier | 3.3 MB |
| 3 | Sep 23, 16:48 | brainstorm | 6 h 14 m | 2.8–4.2 GB (17.6%) | 34 s (≈3.5 cores) | 48 min earlier | 1.7 MB |
| 4 | Sep 23, 16:52 | wallet (new) | 3 h 11 m | 0.5 GB | 8 s (≈1 core) | 1 h 8 m earlier | 4.1 MB |
Case 1 ran 2.1.278; cases 2–4 ran 2.1.280.
In every case SIGTERM was ignored while the process was burning CPU: 363.8%, 336.4%, 361.9% and 100.0% respectively. In case 2 the user had tried to leave the session about three minutes earlier — no keypress ever reached it.
System impact
| | Before kill | After kill |
|---|---|---|
| Case 1 | free memory 43%, swap 7.7/9.2 GB | free 71% |
| Case 2 | free 57%, swap 9.2/10.2 GB | free 71% |
| Case 3 | free 44%, swap 8.4/9.2 GB | free 68% |
Load average reached 8.8–9.2 with no other significant load.
This is not simply "long session = big heap"
A control session on the same machine, same day, same version:
| | brainstorm (failed) | test (healthy) |
|---|---|---|
| Age | 6 h 14 m | 6 h 19 m |
| RSS | 2.8–4.2 GB | 0.1 GB |
| CPU | 340–420% | 0.2% |
| Turns | 25 | 25 |
| Tool calls | 92 | 132 |
| Top tools | Bash 58, WebFetch 20, WebSearch 10 | Bash 109, WebFetch 13, WebSearch 9 |
| History | 1.7 MB | 2.6 MB |
I compared failed vs. healthy transcripts on every attribute recorded in the .jsonl: history size, turn count, tool-call count and tool mix, images in context, MCP calls, background tasks, largest tool result, model, and project size on disk. None of them separates the two groups — the smallest project (14 files, 4.9 MB, no git) failed while the largest (18,132 files, 882 MB, git) stayed healthy, and the largest tool result and image count both belong to healthy sessions.
Nothing recorded in the transcript distinguishes sessions that fail from ones that don't, which suggests the retained state never reaches the transcript.
What Should Happen?
- A session that has been idle for an hour should not consume 3–4 cores.
- Keyboard input,
/exitand SIGTERM should keep working. At minimum the event loop should not be starved to the point where the TUI stops reading stdin and signals are never handled — an unresponsive terminal with no way out is much worse than a slow one. - If the heap genuinely cannot be reduced, failing loudly (OOM) would be far better than an unkillable process that drives the whole machine into swap.
Error Messages/Logs
Case 2 - sampled every 10 s after the user tried to exit; the process never exited:
13:29:11 cpu=258% rss=2.4GB
13:29:22 cpu=365% rss=4.7GB
13:29:32 cpu=339% rss=5.1GB
13:29:42 cpu= 58% rss=5.0GB
13:29:52 cpu=331% rss=5.9GB
13:30:02 cpu=409% rss=3.6GB
Case 3 - sampled every 3 s:
4.1GB 4.2GB 3.9GB 3.6GB 3.4GB 2.6GB
Memory is repeatedly reclaimed and immediately regrown: not a monotonic leak,
but a treadmill the process never leaves.
There is no error output - nothing is written to the transcript, and the
process emits no diagnostics at all.
Steps to Reproduce
I have no deterministic trigger — it happens to some sessions and not others under identical use. Observed conditions:
- Run several Claude Code sessions in parallel (5–9), each in its own terminal pane, on macOS.
- Use them normally for several hours: Bash, Edit, WebFetch, WebSearch.
- After roughly 3–14 hours, some — not all — sessions enter the state above. Four out of nine did so within nine hours; others of the same age and workload stayed at 0.1 GB and ~0% CPU.
To confirm the state while the process is still alive:
ps -Ao pid,pcpu,pmem,etime,comm -r | head -5
# a `claude` process above ~10% MEM or ~100% CPU is already on the treadmill
pid=<pid>; t1=$(ps -o time= -p $pid); sleep 10; echo "$t1 -> $(ps -o time= -p $pid)"
# 10 s of CPU per 10 s of wall clock = one core; 40 s = four cores
for i in $(seq 6); do ps -o rss= -p $pid | awk '{printf "%.1fGB ", $1/1048576}'; sleep 3; done
# gigabyte-scale oscillation = GC thrash
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.280 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Terminal.app (macOS)
Additional Information
Environment
- Claude Code 2.1.280, native install:
~/.local/bin/claude -> ~/.local/share/claude/versions/2.1.280, Mach-O x86_64. Also reproduced on 2.1.278. - macOS 15.7.9 (24G830), Darwin 24.6.0, x86_64
- MacBook Pro, Intel Core i7-9750H, 12 cores, 16 GB RAM
- Node v22.23.2
- No stdio MCP servers configured. No
statusLinegit invocations (removed earlier while diagnosing something unrelated).
Not terminal-specific
I picked Terminal.app in the dropdown because that is where the clearest case occurred, but the symptom is independent of the terminal: it reproduced under Terminal.app, Warp and Zentty (a libghostty-based terminal). Case 1 ran under a plain login zsh with no terminal wrapper involved at all.
Possibly related — same machine, previous day, 2.1.278
Three of nine sessions launched within four seconds of each other (a terminal app auto-updated and restored all its panes at once) went to 100% CPU each and stayed there for 67 minutes. sample showed the main thread entirely in JS with no syscalls. Those three each held five CLOSED TCP sockets to the API endpoint; the six healthy sessions held zero:
pid CLOSED ESTABLISHED project CPU
82749 5 1 apro 100.0%
83728 5 1 brainstorm 100.0%
84030 5 1 freelance 100.3%
84111 0 3 rz 0.1%
84300 0 1 centersocial 0.0%
84619 0 5 test 22.8%
84976 0 1 widgets 1.3%
85144 0 3 wallet 0.0%
85189 0 1 wallet 0.0%
Those also ignored SIGTERM and needed kill -9. This may be the same root cause reached from a different direction (a failed or aborted network operation retaining state), or a separate bug — happy to file it separately.
Workaround
Restart sessions every 2–3 hours with /exit + claude --resume <id> before RSS climbs. Once a process is on the treadmill, kill -9 is the only option; the transcript on disk survives and --resume works.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Update: the same hang reproduced with a flat 0.34 GB heap and exactly one core — so GC thrash is not the root cause, just one of its faces.
A fifth occurrence today, same machine, 2.1.280. User-visible symptom identical to the four cases above: the TUI stopped accepting keyboard input entirely, and SIGTERM was ignored (
kill -9required, process was at 100.9% CPU when the signal was sent). But the process internals are completely different from cases 1–4:| | Cases 1–4 (in the report) | Case 5 (today, 18:39) |
|---|---|---|
| RSS | 2.8–6.2 GB | 0.38 GB |
| Heap dynamics | gigabyte-scale sawtooth | did not move by a single MB over 15 s |
| CPU | 34–40 s per 10 s wall (3–4 cores) | 10 s per 10 s wall (exactly one core) |
| CLOSED sockets | — | 0 |
sampleoutput header confirms the heap never grew at all during the session's 5-hour life:Peak == current, i.e. this process never exceeded 341 MB in five hours. There is no memory growth to collect, so garbage collection cannot be what is burning the core.
All 3586 of 3586 main-thread samples are in a deep chain of pure JS with no syscall at the bottom of the stack:
The frames are unsymbolicated (stripped binary) — I can send the full 25-thread sample file privately if that helps, or run it again with whatever instrumentation you suggest. The
<unknown binary>frames are JIT-compiled code.What this changes
The common root looks like an unterminated loop on the main thread, not a memory problem:
kill -9is the only way out in every single case.So the title's "GC thrash" describes a symptom of the memory-heavy variant, not the underlying defect. The invariant across all five cases is: main thread never yields, process becomes unreachable through every input path, only SIGKILL works.
The worst part for users is not the CPU — it is that a session reaching this state cannot be exited normally, and any in-flight work is lost.
Possibly related data point from Claude Code 2.1.283 on macOS 26.4.1 (arm64). This shares the high CPU and failure to exit after SIGTERM reported in case 5, but adds a different sampled stack signature and three zombie children.
queue-operation, about 15.7 hours before capture; the exact freeze onset and trigger are unknown.R+,psreported 96.3% CPU, physical footprint 538.9 MB (peak 847.1 MB), and 16 threads.<defunct>) children and one live child associated with a backgroundMonitorwatcher.samplecollected 4,187 main-thread samples at a 1 ms interval. Of these, 3,654 (87%) ended at_sigtramp + 0. Abbreviated excerpt below; offsets are relative to the 2.1.283 binary's load address:The case-5 excerpt does not show these signal-trampoline frames. A signal-handling loop, possibly involving child exits, is a hypothesis; this capture does not identify the signal or establish a connection to the zombie children.
This is one captured occurrence, without a confirmed reproduction. I can provide a redacted full sample if useful.
@willmcginnis thanks — useful data point. I can turn one of your observations into a confirmed fact, since you only had the 16-line excerpt to go on.
I still have the complete sample from case 5 (all 25 threads, 295 KB). Checked across the whole file, not just the main-thread excerpt:
_sigtramp,__sigreturn,sigaction,sigsuspend,sigprocmask— 0 occurrences in any threadwait4,waitpid,SIGCHLD— 0 occurrencesSo the absence of signal-trampoline frames in case 5 is real, not an artifact of the abbreviated excerpt. Our two captures have genuinely different hot paths.
What case 5 has instead: the only non-JS leaves on the main thread are clock reads —
_mach_boottime_usec,mach_absolute_time,gettimeofday,clock_gettime(233 and 83 samples for the first two). A loop repeatedly reading the clock looks more like a timer/poll path than signal handling.One gap on my side: I did not check for zombie children on any of my five occurrences — the processes were killed before I thought to look, so I cannot confirm or rule out your child-exit hypothesis for the memory-heavy variant either.
Suggestion for anyone else who hits this, so the captures stay comparable — all of it before killing the process:
That set distinguishes the variants seen so far: heap flat vs. sawtooth, one core vs. several, signal frames present or not, children present or not. A handful of captures should show whether this is one defect with several faces or more than one.
I can attach the full case-5 sample here if a maintainer wants it.