[BUG] JavaScript heap OOM crash on long sessions — unbounded in-memory message accumulation

Resolved 💬 4 comments Opened Feb 15, 2026 by aabolfazl Closed Mar 16, 2026

Preflight

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Code crashes with FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory during long agentic sessions with heavy tool use (many file reads, bash commands, subagent spawns).

The V8 heap grows to ~4.1 GB and hits the default 4.2 GB limit, then the process is killed with SIGABRT (core dumped).

Root cause analysis of the minified cli.js (v2.1.39) reveals:

The mutableMessages display/UI array grows monotonically — every message (user, assistant, tool_use, tool_result) is pushed and never freed. While autocompact trims what gets sent to the API (token-level), the in-process JavaScript objects for UI rendering, transcript, and file history snapshots are never released.

Key findings from the bundled source:

  1. mutableMessages array (CRITICAL): All messages pushed, never removed. Autocompact only replaces API messages via $t(), not the display array.
  2. SDK receivedMessages[] (HIGH): Both stream classes (Cy1, py1) maintain parallel messages[] and receivedMessages[] arrays per API call — if not dereferenced, they retain duplicate data.
  3. Tool results < 400KB stay in RAM (HIGH): Only results above Kb1=400000 chars get persisted to disk. 50 tool calls × 300KB = 15MB in memory.
  4. Output buffers 64MB each (HIGH): sD1 class allows 64MB per tool execution (Ik7=67108736). Multiple concurrent tools multiply this.
  5. File history snapshots (MODERATE): snapshots[] grows linearly with file edits, never trimmed.
  6. UUID dedup Set (MODERATE): Memoized Set<string> of all message UUIDs grows forever per session.

There is no aggregate memory budget — individual items have per-item caps but no total memory limit on the messages array or the process.

What Should Happen?

Claude Code should manage memory during long sessions so the process doesn't OOM. Possible fixes:

  1. Memory-aware compaction: Trigger compaction based on process.memoryUsage().heapUsed, not just token count
  2. Evict old display messages: Trim the UI/display messages array when memory pressure is high
  3. Lower persistence threshold: Persist tool results to disk more aggressively (e.g., >50KB instead of >400KB)
  4. Dereference SDK stream objects: Null out Cy1/py1 instances and their receivedMessages[] after each API turn
  5. Bound file snapshots: Limit snapshots[] to a fixed size (e.g., last 50)
  6. Set --max-old-space-size in launcher: Default to a higher heap limit on systems with sufficient RAM

Error Messages/Logs

<--- Last few GCs --->

[4309:0x55c39612b000] 9098736 ms: Scavenge (during sweeping) 4062.9 (4085.1) -> 4057.3 (4085.6) MB, pooled: 0.0 MB, 19.81 / 0.04 ms (average mu = 0.353, current mu = 0.358) task;
[4309:0x55c39612b000] 9100559 ms: Mark-Compact (reduce) 4077.6 (4099.1) -> 4055.1 (4072.6) MB, pooled: 0.0 MB, 3 0.61 / 0.03 ms (+ 1516.4 ms in 325 steps since start of marking, biggest step 5.1 ms, walltime since start of marking 1821 ms) (average mu = 0)
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

----- Native stack trace -----
1: 0x55c36e1f78f8 node::OOMErrorHandler(char const*, v8::OOMDetails const&) [claude]
12: 0x55c36e5671d9 node::PerIsolatePlatformData::RunForegroundTask(...) [claude]
13: 0x55c36e569c38 node::PerIsolatePlatformData::FlushForegroundTasksInternal() [claude]
16: 0x7f147ba885a2 uv_run [/usr/lib/libuv.so.1]
17: 0x55c36e37a5d3 node::SpinEventLoopInternal(node::Environment*) [claude]

[1]    4309 IOT instruction (core dumped)  claude

Steps to Reproduce

  1. Start Claude Code in a project directory
  2. Have a long agentic session with heavy tool use — many file reads, bash commands, grep searches, web fetches, and/or subagent spawns (Task tool)
  3. Continue the session for an extended period without using /clear (in my case ~2.5 hours based on the 9100559ms GC timestamp)
  4. The process crashes with SIGABRT / JavaScript heap out of memory

Note: The longer the session and the more tool-heavy the usage, the faster memory grows. Sessions with lots of large tool results (file reads, bash output) hit OOM sooner.

Environment

  • Claude Model: Claude Opus 4.6
  • Claude Code Version: 2.1.39
  • Platform: Anthropic Console (api.anthropic.com)
  • Operating System: Linux (Manjaro 6.12.68-1-MANJARO, x86-64, 32GB RAM)
  • Terminal/Shell: tmux / zsh
  • Is this a regression?: Unknown

Additional Information

Core dump details

From coredumpctl info 4309:

  • PID: 4309, Signal: SIGABRT (6), Executable: /usr/bin/node
  • 12 threads active at crash (main + 11 workers)
  • Core dump size: 588.4 MB
  • Default V8 heap limit: 4288 MB, heap at crash: ~4085 MB
  • System had 32GB RAM with 22GB available — NOT a system memory issue

Defenses vs Gaps in current code

| Defense | Status | Gap |
|---------|--------|-----|
| Autocompact | Present | Token-based, not memory-based; can be disabled |
| Tool result persistence to disk | Present | Only for results > 400KB |
| File cache LRU | Present (100 entries, 25MB) | Per-instance; cloned for subagents |
| Bash output truncation | Present (30-150KB) | Accumulated results still in messages |
| Image compression | Present (5MB limit) | Base64 inflation; accumulates between compactions |
| Aggregate memory limit | ABSENT | No total memory cap on process |
| Message array size limit | ABSENT | No max number of in-memory messages |

View original on GitHub ↗

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