thedotmack/claude-mem · info · ChromaMcpConnectionCancelledError

chroma-mcp call cancelled during shutdown

Error message

chroma-mcp call cancelled during shutdown

What it means

ChromaMcpConnectionCancelledError thrown when the first chroma-mcp client.callTool() attempt fails with a transport error AND the manager's connectionGeneration has changed in the meantime, meaning a reconnect/dispose (typically triggered by shutdown) invalidated this call. Rather than retrying a stale connection, the manager cancels the in-flight call so it does not race with the new connection lifecycle.

Solutions

  1. Catch ChromaMcpConnectionCancelledError and treat it as a cancellation, not a data error — retry the operation after the manager is reconnected or restarted.
  2. Check whether shutdown was expected; if the call must complete, ensure no concurrent dispose/reconnect runs while a tool call is in flight.
  3. Add a coordination layer that stops issuing callTool requests once shutdown begins.
  4. Update the chroma-mcp runtime (uvx/uv) if transport errors are frequent, indicating an unstable subprocess.
  5. Enable CHROMA_MCP debug logging to confirm the generation mismatch and identify what bumped connectionGeneration.

Example fix

// before
const result = await manager.callTool('chroma_query_documents', { query: q });
// after
try {
  const result = await manager.callTool('chroma_query_documents', { query: q });
} catch (e) {
  if (e instanceof ChromaMcpConnectionCancelledError) {
    return null; // cancelled by shutdown; retry later
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

function isCancelled(e: unknown): e is Error {
  return e instanceof Error && e.message.includes('call cancelled during shutdown');
}

Try / catch

try {
  await manager.callTool('chroma_query_documents', args);
} catch (e) {
  if (e instanceof ChromaMcpConnectionCancelledError) return null; // benign shutdown cancellation
  throw e;
}

Prevention

When it happens

Trigger: client.callTool() throws a transport error while, concurrently, another code path bumps connectionGeneration (disposeCurrentSubprocess/reconnect during shutdown or connection reset). The generation check after the failed attempt detects the staleness and throws.

Common situations: chroma-mcp subprocess crashes (uvx/python dies, OOM kill) at the same moment the user quits the app; transport drop during shutdown triggers the reconnect path but the generation token has already moved.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/81fdee5f3b221aa5. Report an issue: GitHub.

Appendix: source

Thrown at src/services/sync/ChromaMcpManager.ts:831

    await this.ensureConnected();

    logger.debug('CHROMA_MCP', `Calling tool: ${toolName}`, {
      arguments: JSON.stringify(toolArguments).slice(0, 200)
    });

    let result;
    try {
      result = await this.client!.callTool({
        name: toolName,
        arguments: toolArguments
      });
    } catch (transportError) {
      logger.warn('CHROMA_MCP', `Transport error during "${toolName}", reconnecting and retrying once`, {
        error: transportError instanceof Error ? transportError.message : String(transportError)
      });

      if (callGeneration !== this.connectionGeneration) {
        throw new ChromaMcpConnectionCancelledError('chroma-mcp call cancelled during shutdown');
      }

      // Tree-kill the dying subprocess before reconnect. Previously this path
      // just nulled the handle, which on Linux leaks the uv/python/chroma-mcp
      // descendants every time a transport error happens (#2313).
      await this.disposeCurrentSubprocess();

      try {
        if (callGeneration !== this.connectionGeneration) {
          throw new ChromaMcpConnectionCancelledError('chroma-mcp call cancelled during shutdown');
        }
        await this.ensureConnected();
        result = await this.client!.callTool({
          name: toolName,
          arguments: toolArguments
        });
      } catch (retryError) {
        this.connected = false;

View on GitHub (pinned to d8bc9755e7)