[BUG] Terminal pane fails with posix_spawnp ENOMEM when many sessions are open (no eviction of dormant claude CLI processes)

Open 💬 3 comments Opened May 18, 2026 by jake-smart

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?

Summary

The Claude desktop app starts one persistent claude CLI process per session and never reaps it during the app's lifetime. With many sessions accumulated over a long uptime, the cumulative ~200-300 MB-per-session footprint exhausts physical memory and swap. Subsequent posix_spawnp calls — including the one that launches the embedded terminal pane's /bin/zsh — fail with ENOMEM. The user-visible symptom is "the terminal pane silently doesn't open" across all sessions, including newly created ones.

Environment

| | |
|---|---|
| Claude desktop app | 1.7196.0 |
| Embedded claude-code SDK | 2.1.138 |
| macOS | 14.4.1 (23E224) |
| Hardware | Apple M2 Max, 32 GB RAM, 4 GB swap |
| App uptime when reproduced | ~21 hours |
| Active sessions in sidebar | ~52 (one per recently-used git worktree) |

Reproduction

  1. Open the Claude desktop app and start a session in many different working directories (e.g. multiple git worktrees of the same repo).
  2. Continue using the app over a long uptime, opening new sessions without explicitly closing old ones. There is no user-visible "close session" action and no LocalSessions.close/stop/destroy log event — so they remain alive in the background.
  3. After enough sessions accumulate that combined RSS approaches physical-memory-plus-swap headroom, click into the terminal pane of any session (or create a new session and click its pane).
  4. Observe posix_spawnp failed in ~/Library/Logs/Claude/main.log; the pane silently fails to start.

Expected

Either:

  • (a) Idle eviction. After N minutes of session inactivity, terminate the claude CLI process and free its conversation context; cold-resume from the on-disk transcript on next focus or new message. The --replay-user-messages flag is already in the spawn argv, which suggests cold-resume is supported.
  • (b) Memory-pressure-driven eviction. Register for macOS DispatchSourceMemoryPressure (kMemoryPressureWarn/Critical) and evict least-recently-used unfocused sessions on pressure.

Either way, cumulative footprint should not grow unbounded with session count.

Actual

  • LocalSessions.start has no symmetric tear-down. The CLI process spawned for a session lives until the desktop app fully quits.
  • No memory-pressure response observable.
  • Once posix_spawnp starts failing, retries continue at ~1 Hz with companion [Coder] workspace discovery failed: coder CLI not found on augmented PATH lines, with no backoff and no user-visible error.

Evidence

Log excerpt (~/Library/Logs/Claude/main.log):

2026-05-18 14:13:50 [error] Failed to spawn shell /bin/zsh for session local_xxxxxxxx {
  error: Error: posix_spawnp failed.
      at spawn (.../app.asar/node_modules/node-pty/lib/index.js:29:12)
      at hCr.startShellPty (.../app.asar/.vite/build/index.js:2413:7843)
      at async Object.startShellPty (.../app.asar/.vite/build/index.js:2543:7239)

This pattern repeats every ~1 second once the threshold is crossed; 18 distinct local_* session IDs failed posix_spawnp over a 90-second window today.

Process state at time of failure:

| Metric | Value |
|---|---|
| Active claude CLI processes | 52 (paired with disclaimer shims = 104 entries in ps) |
| Cumulative Claude-related RSS | ~13 GB |
| Total user RSS | ~28 GB / 32 GB physical |
| Free pages | 6,949 pages × 16 KB ≈ ~110 MB free |
| Swap | 3.3 GB / 4 GB used; 17M historical swapouts |
| Oldest claude CLI age | 20 h 55 m — started within seconds of app launch and never replaced |
| kern.maxprocperuid headroom | 453 / 5333 — process count is fine, this is purely RSS pressure |

LocalSessions lifecycle methods observed in logs:

start, startShellPty, stopShellPty, interrupt, setFocusedSession,
sendMessage, cancelQueuedMessage, updateSession, replaceRemoteMcpServers,
respondToToolPermission, setPermissionMode, getPrChecks,
getPrStateForBranch, checkGhAvailable, getDefaultPermissionMode,
checkTrust, popBackgroundTaskSuggestion

Notably present: stopShellPty (terminal-pane shells are reaped on session switch — confirmed by 30+ log entries today).
Notably absent: any LocalSessions.stop/close/destroy symmetric with start. The session's claude CLI has no observable tear-down call site short of app quit.

Root cause (best inference from outside)

The session lifecycle treats every started session as permanently hot — a "browser tab" model — but lacks the counterbalancing eviction that would make that sustainable:

  1. No idle eviction with cold-resume from the on-disk transcript (transcripts live under /private/tmp/claude-501/<encoded-path>/<session-uuid>/; 205 such directories exist on my machine vs 52 live processes, suggesting cold transcripts already persist correctly).
  2. No DispatchSourceMemoryPressure handler.
  3. No bounded retry on posix_spawnp failure.
  4. No user-visible "close session" action (sessions look ephemeral in the sidebar but each costs ~210 MB).

For a user with 3-5 sessions this is invisible. For users working across many parallel worktrees, cumulative footprint exhausts memory and breaks posix_spawnp system-wide.

Proposed fixes (in order of impact)

  1. Idle eviction with cold-resume. After N minutes of session inactivity, terminate the claude CLI process; cold-resume on next focus via --replay-user-messages. The structural fix.
  2. Memory-pressure-driven eviction. Register for macOS memory-pressure notifications; on Warn or Critical, evict LRU unfocused sessions before the system enters thrash.
  3. Bounded backoff on posix_spawnp failure + a surfaced error (toast / inline pane error) so the user knows the pane didn't start. Stop the coder CLI probe on the same retry loop.
  4. User-visible session close. A "close this session" action and per-session memory cost in the sidebar.

Workaround

Identify and kill long-running claude CLI processes for dormant sessions, or fully quit and relaunch the desktop app:

ps -axww -o pid,etime,rss,command | grep "claude.app/Contents/MacOS/claude" | grep -v grep

Killing disclaimer/claude pairs for completed work-streams reclaims ~210 MB each and immediately restores terminal-pane spawn.

What Should Happen?

Claude desktop app shouldn't have side effects on the machine it runs that prevents creation of new shells. Likely need to address the reaping logic for long dormant sessions.

Error Messages/Logs

2026-05-18 14:13:50 [error] Failed to spawn shell /bin/zsh for session local_xxxxxxxx {
  error: Error: posix_spawnp failed.
      at spawn (.../app.asar/node_modules/node-pty/lib/index.js:29:12)
      at hCr.startShellPty (.../app.asar/.vite/build/index.js:2413:7843)
      at async Object.startShellPty (.../app.asar/.vite/build/index.js:2543:7239)

Steps to Reproduce

Make a bunch of claude sessions and keep them around for awhile.

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

1.7196.0

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

_No response_

View original on GitHub ↗

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