Skip to content

chroma-mcp subprocess leak on macOS β€” zombie uv+python3.13 process pairs accumulate across sessionsΒ #2950

Description

@ZSI2017

Before submitting


πŸ“ Manual Bug Report

Bug Description

When CLAUDE_MEM_CHROMA_ENABLED=true (default), zombie uv +
python3.13 process pairs accumulate over time until the system has
hundreds of processes consuming several GB of memory. Observed: 600+
python processes consuming ~6.5 GB of RAM, requiring a manual
kill-all to recover.

Steps to Reproduce

  1. Enable CLAUDE_MEM_CHROMA_ENABLED=true (this is the default)
  2. Open 3–5 Claude Code sessions simultaneously across different
    projects
  3. Use Claude actively for several hours (generating
    observations/syncs)
  4. Run ps aux | grep python3 | grep -v grep | wc -l

The process count grows proportionally to: session count Γ— sync
frequency Γ— kill failure rate.

Expected Behavior

Old subprocess should be fully terminated before spawning a new one.
If killProcessTree fails, it should retry or use process group kill
(kill -pgid) instead of silently swallowing the error.

Root Cause Analysis

Three issues combine to produce this:

  1. killProcessTree is best-effort and silently swallowed

In worker-service.cjs, when chroma-mcp closes unexpectedly:

  t.killProcessTree(u).catch(d => {
    v.debug("CHROMA_MCP", "Background tree-kill after onclose finished
  (best-effort)", ...)
    // error is silently ignored β€” old process keeps running
  })

On macOS, kill -9 on the uv parent does not reliably kill the
python3.13 child. The child becomes an orphan. Since the error is
swallowed, there is no retry or fallback.

  1. Short-connection pattern β€” no connection reuse

Every chroma sync spawns a new uvx --python 3.13 chroma-mcp
subprocess, connects, uses it, then closes. Connections are never
reused. This results in 30+ connect/disconnect cycles over a few
hours of active use.

  1. Concurrent sessions cause lock contention β†’ timeout β†’ reconnect
    loop

Multiple Claude Code sessions each try to connect to the same chroma
persistent data dir simultaneously. The file lock causes connection
attempts to hang past the 30s timeout, each leaving an un-killed uv

  • python3.13 pair. The next retry spawns a new pair on top of the
    orphaned ones.

Suggested Fixes

  1. Use process group kill on macOS/Linux instead of single-PID kill:
    process.kill(-pgid, 'SIGKILL') // kills entire process group
  2. Reuse the chroma-mcp connection across sync operations instead of
    spawn-per-sync
  3. Add a concurrency lock so only one worker connects to the local
    chroma data dir at a time
  4. Verify kill success before spawning a new subprocess

Environment

  • Claude-mem version: 13.4.2
  • Claude Code version: 2.1.169
  • OS: macOS 26.5.1
  • Platform: arm64

Logs

[WARN ] [CHROMA_MCP] Connection failed, killing subprocess tree to
prevent zombie {error=MCP connection to chroma-mcp timed out after
30000ms}
[ERROR] [CHROMA_MCP] Connection attempt failed MCP connection to
chroma-mcp timed out after 30000ms
[ERROR] [CHROMA_SYNC] Backfill failed for project: MCP
connection to chroma-mcp timed out after 30000ms

[WARN ] [CHROMA_MCP] chroma-mcp subprocess closed unexpectedly,
applying reconnect backoff

The timeout pattern repeated 4 times in a single day (roughly every
1–4 hours of active use), and the unexpected-close event occurred
once additionally.

Additional Context

Workaround: Set CLAUDE_MEM_CHROMA_ENABLED=false in
~/.claude-mem/settings.json. This disables semantic search but
eliminates the leak entirely.

The uvx command used to spawn chroma-mcp:
uvx --python 3.13 --with onnxruntime>=1.20 --with protobuf<7
chroma-mcp==0.2.6 --client-type persistent

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed bug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions