.maestro/playbooks/2026-03-29-Issues-Triage-3-29-2026/Phase-04-ChromaDB-Subprocess-Lifecycle.md
This phase fixes the fire-and-forget subprocess management in the ChromaDB/MCP subsystem, which is the root cause of 8+ issues: process leaks consuming CPU/memory, proxy failures, initialization races, and chroma-mcp zombie accumulation. The core problem is that ChromaMcpManager spawns a uvx chroma-mcp subprocess but has no health monitoring, no resource limits, and no reliable cleanup when the subprocess wedges.
Add health monitoring and automatic recovery to ChromaMcpManager. Currently the manager detects subprocess death only via transport close events, which don't fire when the process hangs (CPU spin, deadlock). To fix:
src/services/sync/ChromaMcpManager.ts thoroughly — understand the full lifecycle: ensureConnected(), callTool(), stop(), and the transport close handler (around lines 166-181)chroma_list_collections or similar no-op call through the MCP transport with a 10-second timeoutlogger.error('CHROMA', 'Health check failed, restarting subprocess')stop() to kill the current subprocesscallTool() triggers ensureConnected() with fresh subprocessstop() to prevent leaksRECONNECT_BACKOFF_MS (10 seconds) to avoid rapid restart loops after health-triggered restarts Fix chroma-mcp CPU spin on failed initialization. When uvx fails (Python not found, package download error, network timeout), the subprocess can enter a busy-wait loop that consumes 100% CPU. To fix:
ChromaMcpManager.ts spawnChromaMcp() (around lines 188-234) to understand how the subprocess is launchedprocess.cpuUsage() of the parent or by reading /proc/<pid>/stat on Linux / ps -p <pid> -o %cpu on macOSmaxConsecutiveFailures counter (default: 3). After 3 failed connection attempts, disable Chroma for the session and log: "Chroma disabled after 3 failed connection attempts. Vector search unavailable — SQLite search still active." Fix the chroma-mcp process tree cleanup to prevent zombie accumulation. The current aggressiveStartupCleanup() in ProcessManager.ts kills chroma-mcp processes immediately on startup, but spawned uvx children (the actual Python process) can survive parent death. To fix:
src/services/infrastructure/ProcessManager.ts aggressiveStartupCleanup() (around lines 450-574) and cleanupOrphanedProcesses() (around lines 314-431)kill(-pgid, SIGTERM) (process group kill) instead of individual PID kill to ensure all child processes of uvx are terminated. Check if setsid is used when spawning (it is for the worker, but verify for chroma-mcp)taskkill /T /F should handle tree kills — verify it actually works for uvx/Python child processes by checking if the /T flag traverses the full process treeChromaMcpManager.stop(), add a verification step: after sending SIGTERM/SIGKILL, wait 2 seconds and check if the PID is still alive. If so, force kill again~/.claude-mem/worker.pid) as a chromaPid field, so orphan cleanup can find it even if the worker crashes without cleanupImplement graceful degradation when Chroma is unavailable. Currently, Chroma failures can cause cascading errors in the observation pipeline. To fix:
src/services/worker/agents/ResponseProcessor.ts (around lines 195-218) for the fire-and-forget Chroma sync patternsrc/services/sync/ChromaSync.ts for syncObservation() and queryDocuments()chromaAvailable flag to DatabaseManager or ChromaMcpManager that is checked before attempting any Chroma operations — skip Chroma calls entirely when disabled instead of attempting and catching errors each time"Chroma unavailable — falling back to SQLite-only search. Vector search disabled.""Chroma reconnected — vector search restored."chromaAvailable: boolean field so the UI can indicate search modeWrite tests for ChromaDB lifecycle management:
Run build and verify:
npm run build-and-syncworker-service.cjs includes the new health monitoring logic.then().catch() without proper error state management) in Chroma-related code