Skip to content

[BUG] [Memory Leak] Claude consumes 30GB of RAM On a 16GB Macbook M4 Air #95773

Description

@Invinciblebond

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report (please file separate reports for different bugs)
  • I am using the latest version of Claude Code

What's Wrong?

Image

after 2 minutes:
Image

another minute:
Image

just keeps ramping up.

What Should Happen?

tokensave serve leaks memory unbounded (~1.7 MB/s per process) and pins a CPU core at 99% — grows to tens of GB, exhausts swap, drives sustained thermal load on Apple Silicon

Version: tokensave 7.0.2
Install path: ~/.local/bin/tokensave (155 MB binary, dated Jun 24)
Transport: stdio MCP server, launched by Claude Code / Claude Desktop
OS: macOS 15.7 Sequoia (build 24G222), Darwin 24.6.0, arm64
Hardware: MacBook Air, Apple M4 (10 cores), 16 GB RAM, 256 GB SSD


Summary

Every tokensave serve process started by Claude Code enters an unbounded growth state shortly after launch: resident memory climbs linearly and without plateau while CPU sits pinned at ~99% of one core, indefinitely. The process never stabilizes, never releases, and never self-terminates. It has to be killed manually from Activity Monitor or by quitting Claude entirely.

This is not a slow drift. It is a steady, measurable leak, and because Claude Code spawns one tokensave serve per session, the rate multiplies by the number of open sessions.

On a 16 GB fanless MacBook Air this is not a cosmetic issue. It fills physical RAM, saturates the 4 GB swap file, forces the memory compressor to run continuously, and — because the leak is paired with a permanently spinning CPU core — produces sustained heat on a machine with no active cooling. I have observed idle-state package temperatures reaching 105 °C with nothing else running. That is a hardware-stress condition caused by a background MCP server the user never explicitly invoked.


Measured evidence

Two tokensave serve processes were running (two open Claude sessions). Sampled with ps -o pid,pcpu,rss,etime every 10 seconds:

06:12:14   pid 4175   93.3% CPU   280432 KB RSS   elapsed 02:26
           pid 4340   96.0% CPU   249104 KB RSS   elapsed 02:15
06:12:24   pid 4175   91.7% CPU   297360 KB RSS   elapsed 02:36
           pid 4340   95.1% CPU   266320 KB RSS   elapsed 02:25
06:12:34   pid 4175   99.2% CPU   314688 KB RSS   elapsed 02:46
           pid 4340   98.1% CPU   283632 KB RSS   elapsed 02:35
06:12:44   pid 4175   95.1% CPU   332128 KB RSS   elapsed 02:56
           pid 4340   98.7% CPU   300704 KB RSS   elapsed 02:45
06:12:54   pid 4175   99.0% CPU   343984 KB RSS   elapsed 03:03
           pid 4340   99.6% CPU   312544 KB RSS   elapsed 02:52

Growth rate: ~17.3 MB per 10 s, per process → ≈1.73 MB/s each, ≈3.5 MB/s combined.
Extrapolated: ≈208 MB/min → ≈12.5 GB/hour with two sessions open. The curve is dead linear across the whole sample window — there is no GC, no cap, no backoff.

Virtual size: VSZ reads 411,485,488 KB (~392 GB) per process. Activity Monitor reports the figure it derives from this plus compressed pages, which is why the user-visible "Memory" column climbs from under 1 MB to tens of GB within minutes — matching the ~30 GB readings I see before I intervene.

CPU: both processes hold 91–99% continuously. 13 threads each. Neither drops to idle at any point, including while Claude itself is idle and awaiting user input.

System impact at time of capture:

vm.swapusage: total = 4096.00M  used = 3071.44M  free = 1024.56M
Pages free:        21122   (≈330 MB free of 16 GB)
Pages active:     401973
Pages inactive:   399775

Swap is 75% consumed. On a 256 GB SSD, hours of this per day is also a meaningful write-amplification concern for NAND endurance.


Probable root cause: unbounded recursive filesystem walk escaping the session working directory

This is the part that I think matters most, and it is reproducible in the lsof output.

pid 4340 was launched with its working directory set to an empty, freshly created scratch workspace:

cwd  DIR  .../scratch-workspaces/68e613fe-.../scratch-2026-09-21-1f0e92

That directory contained zero files. Despite that, the same process held open directory handles four levels deep inside an unrelated node_modules tree on the Desktop:

15r DIR  /Users/main/Desktop/.../wallet-widget copy/node_modules/react-native/ReactCommon/react/utils
16r DIR  /Users/main/Desktop/.../node_modules/react-native/ReactCommon/react/utils/platform
17r DIR  /Users/main/Desktop/.../node_modules/react-native/ReactCommon/react
18r DIR  /Users/main/Desktop/.../node_modules/react-native/ReactCommon/react/utils/platform/android

A subsequent sample of the same PID showed it had moved on to a completely different tree — a Data/Cache/effect/... cache hierarchy — and pid 4175 (cwd /Users/main/Downloads/Shade) was simultaneously walking its own Data/Cache/effect/... path.

So: the indexer is not scoped to the project root. It walks outside the session's working directory, descends into node_modules and into application cache directories, and does so with no apparent ignore-list, no depth limit, and no size ceiling. On a developer machine, that search space is effectively infinite — every node_modules in every stale project copy, every browser/app cache, every .git object store. Each walked entry appears to be retained rather than streamed, which is consistent with both the linear RSS growth and the pinned CPU.

Config in use (~/.tokensave/config.toml) is stock, with watcher_debounce = "2s" and extraction_timeout_secs = 60. The 60 s extraction timeout does not appear to bound the walk itself, and the on-disk databases stay small (tokensave.db 385 KB, global.db 57 KB, total ~/.tokensave = 508 KB) while RSS goes to gigabytes — the memory is being held in-process, not flushed to the store. That asymmetry is the strongest single indicator of a leak rather than legitimate indexing work.


Steps to reproduce

  1. macOS machine with a normal developer home directory (several projects containing node_modules, app caches under ~/Library).
  2. tokensave 7.0.2 installed at ~/.local/bin/tokensave, registered as a stdio MCP server:
    { "type": "stdio", "command": "/Users/<user>/.local/bin/tokensave", "args": ["serve"], "env": {} }
  3. Open a Claude Code session — any session, including one whose working directory is an empty folder.
  4. Watch the process:
    while true; do ps -o pid,pcpu,rss,etime,command -c -p $(pgrep -d, -f "tokensave serve"); sleep 10; done
  5. Observe RSS climbing ~1.7 MB/s per process with CPU at ~99%, never plateauing.
  6. lsof -p <pid> | awk '$5=="DIR"' — confirm open directory handles outside the session cwd.

Expected: indexing scoped to the project root, bounded memory, CPU returning to idle once the initial index completes.
Actual: unbounded RSS growth, permanent 99% CPU, directory traversal outside the project root, no termination condition.


Impact

  • Memory exhaustion: 16 GB RAM + 4 GB swap consumed within minutes to tens of minutes depending on session count. System becomes unresponsive; other applications are compressed/swapped out.
  • Thermal: one fully pinned core per session on a fanless M4 Air. Observed idle-state temperature of 105 °C. Sustained operation at that level drives aggressive throttling and, over time, is a legitimate concern for component longevity on a passively cooled chassis.
  • SSD wear: continuous swap churn on a 256 GB drive.
  • Forced manual intervention: the only remedies are killing the process in Activity Monitor or restarting Claude — several times per working day.
  • Silent onset: nothing in the Claude UI indicates the MCP server has gone runaway. The user only finds it by opening Activity Monitor and noticing the machine is hot.

Suggested fixes

  1. Scope the walk to the session's working directory. A process whose cwd is an empty scratch folder should never open handles under ~/Desktop/.../node_modules or ~/Library/.../Cache.
  2. Ship a default ignore-list — node_modules, .git, target, dist, build, vendor, Pods, .venv, ~/Library, and anything marked with the macOS "do not backup"/cache attributes.
  3. Hard-cap resident memory with a configurable ceiling; abort or spill the index when it is hit rather than growing without limit.
  4. Stream to the store instead of accumulating in RAM. The 508 KB on-disk footprint against multi-GB RSS suggests everything is being buffered in memory first.
  5. Bound the traversal — max depth, max entry count, max wall-clock, and honor extraction_timeout_secs for the walk phase too.
  6. Yield the CPU. A background indexer should not hold 99% of a core indefinitely; add throttling/nice-ing and idle down when there is no work.
  7. Deduplicate across sessions. N Claude sessions currently means N full independent crawls of overlapping trees. A single shared daemon or a cross-process lock (a sync.lock file already exists in ~/.tokensave/) would cut this by the session multiplier.

Environment dump

tokensave 7.0.2
macOS 15.7 (24G222) — Darwin 24.6.0 arm64
Apple M4, 10 cores, 17179869184 bytes RAM (16 GB), 256 GB SSD
Binary: /Users/main/.local/bin/tokensave (155060928 bytes, Jun 24 18:11)
Data dir: ~/.tokensave — tokensave.db 385 KB, global.db 57 KB, total 508 KB
Config: upload_enabled = true, watcher_debounce = "2s", extraction_timeout_secs = 60
Host: Claude Code (stdio MCP)

I am happy to collect a heap profile, sample/spindump output, or run an instrumented build if that would help narrow it down.

Error Messages/Logs

Steps to Reproduce

Steps to reproduce
macOS machine with a normal developer home directory (several projects containing node_modules, app caches under ~/Library).
tokensave 7.0.2 installed at ~/.local/bin/tokensave, registered as a stdio MCP server:
{ "type": "stdio", "command": "/Users//.local/bin/tokensave", "args": ["serve"], "env": {} }
Open a Claude Code session — any session, including one whose working directory is an empty folder.
Watch the process:
while true; do ps -o pid,pcpu,rss,etime,command -c -p $(pgrep -d, -f "tokensave serve"); sleep 10; done
Observe RSS climbing ~1.7 MB/s per process with CPU at ~99%, never plateauing.
lsof -p | awk '$5=="DIR"' — confirm open directory handles outside the session cwd.
Expected: indexing scoped to the project root, bounded memory, CPU returning to idle once the initial index completes. Actual: unbounded RSS growth, permanent 99% CPU, directory traversal outside the project root, no termination condition.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

No response

Claude Code Version

latest

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

No response

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions