thedotmack/claude-mem · info · ChromaMcpConnectionCancelledError

chroma-mcp connection cancelled during shutdown

Error message

chroma-mcp connection cancelled during shutdown

What it means

assertConnectionNotCancelled compares the connection generation token passed into the connect flow against this.connectionGeneration. If another thread/code path bumped the generation (e.g. a shutdown or dispose started a new cycle), the in-flight connection is stale and ChromaMcpConnectionCancelledError is thrown to abandon it.

Solutions

  1. No fix needed — this is expected cancellation; catch ChromaMcpConnectionCancelledError and drop the pending connect.
  2. Avoid disposing/restarting the manager while connect() is awaiting; await or cancel connections explicitly first.
  3. If it fires unexpectedly, audit code paths that increment connectionGeneration (dispose, shutdown, reconnect) for races.

Example fix

// before
const p = manager.connect();
manager.dispose(); // cancels in-flight connect -> throws
await p;
// after
const p = manager.connect();
await p.catch(e => { if (!(e instanceof ChromaMcpConnectionCancelledError)) throw e; });
manager.dispose();
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await manager.connect();
} catch (e) {
  if (e instanceof ChromaMcpConnectionCancelledError) {
    return; // expected during shutdown/restart; drop the attempt
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling connect() and then disposing/restarting the manager (shutdown, settings reload, generation bump) before the handshake completes; prewarmChromaMcp or connectInternal checkpoints detect the generation mismatch.

Common situations: App shutdown racing a background reconnect; hot-reloading configuration that tears down and recreates ChromaMcpManager while a connect is in flight; rapid open/close cycles in tests.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

      logger.debug('CHROMA_MCP', 'Background tree-kill after onclose finished (best-effort)', {
        pid,
        error: error instanceof Error ? error.message : String(error)
      });
    } finally {
      this.releaseChromaWriterLock();
    }
  }

  private async waitForUnexpectedCloseCleanup(): Promise<void> {
    const cleanup = this.unexpectedCloseCleanup;
    if (cleanup) {
      await cleanup;
    }
  }

  private assertConnectionNotCancelled(connectionGeneration: number): void {
    if (this.connectionGeneration !== connectionGeneration) {
      throw new ChromaMcpConnectionCancelledError();
    }
  }

  private getLocalPersistentChromaDataDir(): string | null {
    const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
    const chromaMode = settings.CLAUDE_MEM_CHROMA_MODE || 'local';
    return chromaMode === 'remote' ? null : paths.chroma();
  }

  private buildCommandArgs(localChromaDataDir: string | null = this.getLocalPersistentChromaDataDir()): string[] {
    const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
    const pythonVersion = process.env.CLAUDE_MEM_PYTHON_VERSION || settings.CLAUDE_MEM_PYTHON_VERSION || '3.13';
    const launcherPrefix = ChromaMcpManager.buildLauncherPrefix(pythonVersion);

    if (!localChromaDataDir) {
      const chromaHost = settings.CLAUDE_MEM_CHROMA_HOST || '127.0.0.1';
      const chromaPort = settings.CLAUDE_MEM_CHROMA_PORT || '8000';
      const chromaSsl = settings.CLAUDE_MEM_CHROMA_SSL === 'true';

View on GitHub (pinned to d8bc9755e7)