[BUG] Terminal pane fails with posix_spawnp ENOMEM when many sessions are open (no eviction of dormant claude CLI processes)
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
- Open the Claude desktop app and start a session in many different working directories (e.g. multiple git worktrees of the same repo).
- 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/destroylog event — so they remain alive in the background. - 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).
- Observe
posix_spawnp failedin~/Library/Logs/Claude/main.log; the pane silently fails to start.
Expected
Either:
- (a) Idle eviction. After N minutes of session inactivity, terminate the
claudeCLI process and free its conversation context; cold-resume from the on-disk transcript on next focus or new message. The--replay-user-messagesflag 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.starthas 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_spawnpstarts failing, retries continue at ~1 Hz with companion[Coder] workspace discovery failed: coder CLI not found on augmented PATHlines, 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:
- 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). - No
DispatchSourceMemoryPressurehandler. - No bounded retry on
posix_spawnpfailure. - 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)
- Idle eviction with cold-resume. After N minutes of session inactivity, terminate the
claudeCLI process; cold-resume on next focus via--replay-user-messages. The structural fix. - Memory-pressure-driven eviction. Register for macOS memory-pressure notifications; on Warn or Critical, evict LRU unfocused sessions before the system enters thrash.
- Bounded backoff on
posix_spawnpfailure + a surfaced error (toast / inline pane error) so the user knows the pane didn't start. Stop thecoderCLI probe on the same retry loop. - 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_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗