Claude Desktop local-agent (Cowork) sessions leak claude-code node processes (never exit at session end) — GBs of RAM + MCP port exhaustion

Open 💬 1 comment Opened Jun 25, 2026 by TheMikeFactoryMustGrow

Summary

On macOS, local Claude Code sessions launched by the Claude Desktop app (the "Cowork" / local-agent mode, where the Desktop app spawns claude-code/<version>/claude.app/Contents/MacOS/claude node processes) are not reaped when the session ends. The node process keeps running indefinitely after the conversation is over, and it keeps its child MCP-server processes alive with it. Over hours/days these accumulate into dozens of multi-hundred-MB processes, and the surviving MCP children keep holding the OS resources they own — notably TCP ports — which eventually breaks new sessions.

Environment

  • Claude Desktop (macOS) launching local Claude Code sessions and scheduled tasks
  • Claude Code version 2.1.181
  • Per-session process lineage: /Applications/Claude.app/Contents/MacOS/Claude…/Contents/Helpers/disclaimer ……/claude-code/<ver>/claude.app/Contents/MacOS/claude (the node) → MCP-server children

Observed behavior

  • Every local session (and every scheduled-task run) spawns a claude-code node process under the Desktop app.
  • When the session/conversation ends, that node process does not exit. SessionEnd hooks do fire (so the session logically ends), but the process keeps running.
  • Lingering nodes accumulate without bound until the Desktop app is restarted. Observed dozens at once (50+), including processes from sessions started 1–3 days earlier still running. In a fresh snapshot after a restart: ~5 nodes from a single morning's scheduled-task burst, each ~230–380 MB RSS (~1.2 GB total); the count climbs as more sessions run.
  • The lingering node keeps its child MCP servers alive too. Concretely, a stdio MCP server it spawned (in our case the Google Workspace MCP, workspace-mcp) keeps running and keeps holding the local TCP port it bound (we saw 17 workspace-mcp processes alive in one snapshot).
  • The per-session registry files at ~/.claude/sessions/<node_pid>.json are also not cleaned up — they persist after the session ends and carry no heartbeat (mtime is frozen at session start), so they can't be used to tell a live session from a leaked one.

Impact

  1. Unbounded memory leak: dozens of multi-hundred-MB node processes (>1 GB quickly) plus their MCP-server children, until the Desktop app is restarted.
  2. Concrete downstream failure (how we found it): each leaked node's workspace-mcp child keeps holding its OAuth-callback TCP port. workspace-mcp binds within a small fixed port range per account (e.g. 8001–8005). Once leaked instances fill the range, a new session's Google Workspace MCP can't bind and exits with No available port in range → the connector shows "Failed to connect." This presents as an intermittent, confusing auth-looking failure that is actually port exhaustion caused by the process leak.
  3. The only remediation is quitting and reopening the Desktop app.

Steps to reproduce

  1. On macOS, use Claude Desktop's local-agent / Cowork mode (and/or scheduled tasks) to run several Claude Code sessions over time. Have each connect at least one stdio MCP server that binds a local socket (e.g. a Google Workspace MCP).
  2. End each session normally.
  3. ps -ax | grep 'claude-code/.*MacOS/claude' — node processes from ended sessions remain running indefinitely; their MCP-server children remain too. The count grows with each session and never decreases until the Desktop app restarts.

Expected behavior

When a local session ends, its claude-code node process and the MCP-server children it spawned should be terminated, and its ~/.claude/sessions/<pid>.json cleaned up.

Notes / suggested investigation

  • Node process not exiting at session teardown (an open libuv handle / unref'd timer / lingering socket keeping the event loop alive?).
  • MCP child processes not being killed when their owning session ends (and, relatedly, stdio MCP servers that don't exit on stdin EOF can outlive even a dead parent).
  • The session-registry file lacking an end marker / heartbeat leaves external cleanup tooling unable to distinguish active vs ended sessions.

Severity: Medium-high — silent multi-GB memory growth on long-running Desktop installs, plus intermittent MCP failures that look like auth bugs.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗