From c22fcd49b551875919b087ab9dc88dc6a94a72fd Mon Sep 17 00:00:00 2001 From: Manuel Fedele Date: Mon, 16 Mar 2026 09:31:28 +0100 Subject: [PATCH] fix(chroma): close transport before nulling to prevent subprocess leak In callTool(), both the initial catch and retry catch blocks set this.transport = null without calling transport.close() first. When connectInternal() runs next, it sees transport === null, skips the cleanup, and spawns a new chroma-mcp subprocess while the old one is still alive. Over time this leaks dozens of orphaned uvx/chroma-mcp processes, each downloading a 79MB ONNX model, exhausting RAM and causing MCP search timeouts. Fix: save a reference to the stale transport, null the instance field, then close the saved reference. This ensures the subprocess receives SIGTERM before a replacement is spawned. Fixes #1369 --- src/services/sync/ChromaMcpManager.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/services/sync/ChromaMcpManager.ts b/src/services/sync/ChromaMcpManager.ts index abd027655..73f6af98c 100644 --- a/src/services/sync/ChromaMcpManager.ts +++ b/src/services/sync/ChromaMcpManager.ts @@ -252,9 +252,17 @@ export class ChromaMcpManager { // Transport error: chroma-mcp subprocess likely died (e.g., killed by orphan reaper, // HNSW index corruption). Mark connection dead and retry once after reconnect (#1131). // Without this retry, callers see a one-shot error even though reconnect would succeed. + // + // CRITICAL: Close the transport before nulling to kill the subprocess (#1369). + // If we null this.transport first, connectInternal() sees transport === null, + // skips cleanup, and spawns a new subprocess, orphaning the old one. this.connected = false; + const staleTransport = this.transport; this.client = null; this.transport = null; + if (staleTransport) { + try { await staleTransport.close(); } catch { /* subprocess may already be dead */ } + } logger.warn('CHROMA_MCP', `Transport error during "${toolName}", reconnecting and retrying once`, { error: transportError instanceof Error ? transportError.message : String(transportError) @@ -267,7 +275,14 @@ export class ChromaMcpManager { arguments: toolArguments }); } catch (retryError) { + // Retry also failed. Close transport to prevent another orphan. this.connected = false; + const retryTransport = this.transport; + this.client = null; + this.transport = null; + if (retryTransport) { + try { await retryTransport.close(); } catch { /* subprocess may already be dead */ } + } throw new Error(`chroma-mcp transport error during "${toolName}" (retry failed): ${retryError instanceof Error ? retryError.message : String(retryError)}`); } }