Memory scales linearly with open sessions on macOS — idle chats never release RAM, thrashing 16 GB machines

Status Open
Reported on v2.1.237
Maintainer reply None cached
Activity 3 comments · opened Aug 21, 2026

Summary

Each open Claude Code session is an independent process holding its full transcript + the complete tool/MCP-schema payload in its V8 heap, and that memory is never released while the chat stays open — even when the session is idle for days. With several MCP servers enabled, per-session footprint is large and there is no sharing across sessions, so RAM scales linearly with (open chats × enabled servers). On a 16 GB machine this exhausts RAM and sends the system into swap/compressor thrash.

Environment

  • Claude Code 2.1.237 (idle sessions observed running on 2.1.226)
  • macOS 15 (Darwin 25.6.0), Apple M1, 8 cores, 16 GB RAM
  • ~15 MCP servers enabled (several with 60–100 tools each)

Observed

13 concurrent sessions, 7 of them idle for 2+ days:

  • ~110–180 MB RSS per session (undercounts — idle heaps get compressed out of RSS)
  • Load average 26–54 on 8 cores
  • CPU: 26% user / 58% sys / 15% idle — kernel memory management, not real work
  • Memory compressor holding 7.5 GB; swap 8.8 GB of 10 GB used, only ~100 MB free

The high load is not the idle chats computing (they sit <1% CPU) — it's the kernel compressing/decompressing and swapping their pinned heaps whenever one is touched.

Expected

  • Idle sessions should be able to release or hibernate memory (evict transcript buffers / drop schemas) instead of pinning it indefinitely.
  • MCP connections and tool schemas should be shareable across sessions (a pooled broker daemon) rather than duplicated per process — otherwise enabling a rich set of MCP servers makes every additional open chat progressively more expensive.

Additional note (user-reported, not measured)

The user reports this degraded recently rather than being longstanding, suggesting a possible regression. I could not verify this — I have no baseline from before — so flagging it as a report, not a measurement.

View original on GitHub ↗

3 Comments

Tuinslak · 1 month ago

Timing update from the reporter: the degradation set in over the last few days (roughly since ~2026-08-18), not longstanding. Possibly correlates with a recent version update on this machine — idle sessions are running 2.1.226 while the current installed build is 2.1.237, so an update landed within that window. Still a report rather than a bisected regression, but it narrows where to look.

kenneth0227 · 27 days ago

Independent data point on a newer build, with one finding that narrows the fix
space: teardown is not broken — only the idle policy is missing.

Environment

  • macOS (Darwin 27.0.0), Apple Silicon
  • Claude Desktop 2.110.1, Claude Code 2.1.271
  • 10 sessions open, idle 7–19h

1. The retention rule is exactly "opened since app launch", with no bound

The desktop app started at 01:49. At 21:38 there were 10 live session processes.
Cross-referencing against the session list: every session whose lastActivityAt
was after 01:49 had a live process (9, plus the active one); the next-oldest
session — last active 8 days earlier — had none. Exact match, no off-by-one.

So this is not an LRU pool with a cap that happens to be large. Every session
opened since app launch is retained until the app exits, regardless of idle time.

2. Teardown works correctly — verified by controlled experiment

The issue title says memory is "never released". Worth separating two claims:
it is never released automatically, but the release path itself is sound.

Archiving one idle session (18h idle) and re-measuring:

before                                    after
claude CLI  PID 24138                 →   gone
 └─ uv      PID 24176 (MCP sidecar)   →   gone
     └─ py  PID 24219                 →   gone
orphans (ppid=1): 0                       orphans (ppid=1): 0

The whole tree goes, no orphans, immediately. I also verified no orphans existed
in the steady state beforehand — all 11 sidecar trees had valid living parents.

This matters for triage: there is no leak to hunt in the child-process lifecycle.
The entire defect is the absence of an eviction policy above it.

3. The teardown primitive is already in place

Each session tree is its own process group, rooted at the
disclaimer --pgroup wrapper:

PID    PPID   PGID
19825  ...    19825   disclaimer --pgroup --   (group leader)
19826  19825  19825   claude CLI
19853  19826  19825    └─ uv (MCP sidecar)
19914  19853  19825        └─ python

Group-kill semantics are available today; what is missing is something that
decides when to use them.

4. Cost split, and a caveat on comparing to the original report

At 10 sessions:

| layer | count | RSS |
|---|---|---|
| session CLI processes | 10 | 5,604 MB |
| MCP sidecars (one plugin) | 11 trees | 1,606 MB |

~560 MB per session process here, versus the 110–180 MB reported above on
2.1.237. I would not read that as confirming the regression hypothesis —
different machine, different MCP server set, and RSS undercounts compressed idle
heaps differently under different memory pressure. Posting it as a comparable
measurement, not as evidence.

Note the sidecar column is a single plugin that had never been invoked
(usageCount: 0 across 97 startups). Multiply by the number of enabled stdio
servers for the general case — which is the multiplier the original report
describes. Filed separately as #95372, since that one has its own distinct cause.

Practical mitigation for anyone hitting this now

Archiving idle sessions reclaims the full tree immediately and is reversible
(unarchive restores the conversation intact — verified). On this machine that
was ~390 MB per archived session, far more than killing MCP sidecars alone
(~82 MB per tree).

kejid · 14 days ago

"Hibernate idle chats" is what I ended up building as a plugin while waiting for this: https://github.com/kejid/ramsleeper

It lists each session with the memory of its whole process tree (the claude process plus its MCP servers), and unloads the ones you pick. Unloading sends SIGINT so the session exits cleanly. The transcript stays on disk, and the session resumes from it on your next message.

One caveat, since this issue is about macOS: I built and tested it on Windows. The macOS path (ps-based process tree, a menu bar item through xbar/SwiftBar) is written but has never run on a Mac. Linux passes an automated smoke test in Docker. If anyone here is willing to run node tests/smoke.mjs on a Mac and report back, that would tell us quickly whether it works. The test uses fake sessions and doesn't touch real ones.