{"record":{"id":"34e538e42cf8eb0a","repo":"thedotmack/claude-mem","slug":"chroma-mcp-subprocess-closed-unexpectedly-applyin","errorCode":null,"errorMessage":"chroma-mcp subprocess closed unexpectedly, applying reconnect backoff","messagePattern":"chroma-mcp subprocess closed unexpectedly, applying reconnect backoff","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":342,"sourceCode":"    const currentTransport = this.transport;\n    // Captured HERE, while the child is alive and attached — not in the\n    // onclose handler below, which by definition runs after it has died.\n    const transportChild = (this.transport as unknown as { _process?: ChildProcess })._process;\n    const currentTracked = transportChild ? trackChild(transportChild) : null;\n    const currentTrackedPid = currentTracked?.pid;\n    this.transport.onclose = () => {\n      if (this.transport !== currentTransport) {\n        logger.debug('CHROMA_MCP', 'Ignoring stale onclose from previous transport');\n        return;\n      }\n      if (\n        this.connectionGeneration !== connectionGeneration ||\n        this.intentionallyClosingTransports.has(currentTransport as unknown as object)\n      ) {\n        logger.debug('CHROMA_MCP', 'Ignoring onclose from intentionally closed transport');\n        return;\n      }\n      logger.warn('CHROMA_MCP', 'chroma-mcp subprocess closed unexpectedly, applying reconnect backoff');\n      this.connected = false;\n      getSupervisor().unregisterProcess(CHROMA_SUPERVISOR_ID);\n      this.client = null;\n      this.transport = null;\n      this.lastConnectionFailureTimestamp = Date.now();\n\n      // Direct child (uvx) emitted close, but on Linux the grandchildren\n      // (uv/python/chroma-mcp) often outlive their parent because MCP SDK\n      // does not use process groups. Sweep the descendant tree using the\n      // captured PID — best-effort; pgrep returns nothing if everything\n      // already exited (#2313).\n      this.scheduleUnexpectedCloseCleanup(currentTracked);\n    };\n  }\n\n  private scheduleUnexpectedCloseCleanup(tracked: TrackedChild | null): void {\n    let cleanup: Promise<void>;\n    cleanup = this.cleanupUnexpectedCloseSubprocess(tracked).finally(() => {","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaMcpManager.ts#L324-L360","documentation":"ChromaMcpManager spawns a chroma-mcp MCP subprocess to talk to Chroma. When the transport's onclose fires for a transport that was NOT intentionally closed (and the connection generation is still current), the manager logs this warning, tears down client/transport state, and applies a reconnect backoff before re-spawning.","triggerScenarios":"The chroma-mcp child process crashes or exits (OOM, unhandled exception, bad UV/python env), the stdio pipe breaks, or the process is killed externally while ensureConnected considers the connection live.","commonSituations":"chroma-mcp fails to start due to missing uv/Python, the subprocess is OOM-killed under load, the user kills the pid manually, or an OS-level resource limit terminates the child mid-session.","solutions":["Check chroma-mcp server logs/exit code to find why the subprocess died (missing uv, Python version, port/env issues).","Verify uv and the chroma-mcp package are installed and resolvable in the environment the plugin runs in.","Wait for the automatic reconnect backoff to re-spawn the subprocess; no action is needed for transient kills.","Check system logs for OOM-killer events and reduce Chroma collection size/memory pressure.","Ensure no external process manager is killing child processes of the plugin."],"exampleFix":"// before: no visibility into why the child died\n// after: capture exit code and stderr for diagnosis\nchild.on('exit', (code, signal) => {\n  logger.warn('CHROMA_MCP', `chroma-mcp exited code=${code} signal=${signal}`);\n});","handlingStrategy":"retry","validationCode":"// before relying on chroma-mcp, check prerequisites\nconst { stdout } = await Bun.$`uv --version`.nothrow();\nif (stdout.trim() === '') console.error('uv missing; chroma-mcp subprocess will fail');","typeGuard":null,"tryCatchPattern":"// transport onclose is event-based; guard reconnection\nmanager.on('disconnected', () => {\n  // backoff already applied; optionally alert after N rapid reconnects\n  if (manager.reconnectCount > 5) alertOps('chroma-mcp crash-looping');\n});","preventionTips":["Pin and verify uv/Python availability before starting the manager","Monitor subprocess exit codes and stderr","Set container memory limits above chroma-mcp's footprint","Avoid SIGKILLing plugin child processes from external supervisors"],"tags":["subprocess","chroma","reconnect","mcp"],"backgroundTag":"connection-refused","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}