thedotmack/claude-mem · warning
chroma-mcp subprocess closed unexpectedly, applying…
Error message
chroma-mcp subprocess closed unexpectedly, applying reconnect backoff
What it means
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.
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.
Example fix
// before: no visibility into why the child died
// after: capture exit code and stderr for diagnosis
child.on('exit', (code, signal) => {
logger.warn('CHROMA_MCP', `chroma-mcp exited code=${code} signal=${signal}`);
}); Defensive patterns
Strategy: retry
Validate before calling
// before relying on chroma-mcp, check prerequisites
const { stdout } = await Bun.$`uv --version`.nothrow();
if (stdout.trim() === '') console.error('uv missing; chroma-mcp subprocess will fail'); Try / catch
// transport onclose is event-based; guard reconnection
manager.on('disconnected', () => {
// backoff already applied; optionally alert after N rapid reconnects
if (manager.reconnectCount > 5) alertOps('chroma-mcp crash-looping');
}); Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.
Related errors
- chroma-mcp connection failed
- chroma-mcp transport error during
- Connection failed, killing subprocess tree to prevent zombie
- chroma-mcp call cancelled during shutdown
- chroma-mcp connection in backoff
AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17).
Data as JSON: /api/errors/34e538e42cf8eb0a.
Report an issue: GitHub.
Appendix: source
Thrown at src/services/sync/ChromaMcpManager.ts:342
const currentTransport = this.transport;
// Captured HERE, while the child is alive and attached — not in the
// onclose handler below, which by definition runs after it has died.
const transportChild = (this.transport as unknown as { _process?: ChildProcess })._process;
const currentTracked = transportChild ? trackChild(transportChild) : null;
const currentTrackedPid = currentTracked?.pid;
this.transport.onclose = () => {
if (this.transport !== currentTransport) {
logger.debug('CHROMA_MCP', 'Ignoring stale onclose from previous transport');
return;
}
if (
this.connectionGeneration !== connectionGeneration ||
this.intentionallyClosingTransports.has(currentTransport as unknown as object)
) {
logger.debug('CHROMA_MCP', 'Ignoring onclose from intentionally closed transport');
return;
}
logger.warn('CHROMA_MCP', 'chroma-mcp subprocess closed unexpectedly, applying reconnect backoff');
this.connected = false;
getSupervisor().unregisterProcess(CHROMA_SUPERVISOR_ID);
this.client = null;
this.transport = null;
this.lastConnectionFailureTimestamp = Date.now();
// Direct child (uvx) emitted close, but on Linux the grandchildren
// (uv/python/chroma-mcp) often outlive their parent because MCP SDK
// does not use process groups. Sweep the descendant tree using the
// captured PID — best-effort; pgrep returns nothing if everything
// already exited (#2313).
this.scheduleUnexpectedCloseCleanup(currentTracked);
};
}
private scheduleUnexpectedCloseCleanup(tracked: TrackedChild | null): void {
let cleanup: Promise<void>;
cleanup = this.cleanupUnexpectedCloseSubprocess(tracked).finally(() => {View on GitHub (pinned to d8bc9755e7)