Skip to content

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

Description

@aabolfazl

Preflight

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report
  • 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingplatform:linuxIssue specifically occurs on LinuxstaleIssue is inactive

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions