thedotmack/claude-mem · warning · ChromaUnavailableError

chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BAC

Error message

chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BACKOFF_MS - timeSinceLastFailure) / 1000)}s remaining)

What it means

ChromaMcpManager is a singleton that backs off for RECONNECT_BACKOFF_MS (10s) after a connection attempt fails. ensureConnected() computes timeSinceLastFailure; if a failure happened within the backoff window, it throws ChromaUnavailableError (HTTP 503, code CHROMA_UNAVAILABLE) with the remaining seconds rather than hammering chroma-mcp. This protects a struggling subprocess and downstream callers see a typed unavailable error they can degrade on.

Source

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

  }

  static getInstance(): ChromaMcpManager {
    if (!ChromaMcpManager.instance) {
      ChromaMcpManager.instance = new ChromaMcpManager();
    }
    return ChromaMcpManager.instance;
  }

  private async ensureConnected(): Promise<void> {
    await this.waitForUnexpectedCloseCleanup();

    if (this.connected && this.client) {
      return;
    }

    const timeSinceLastFailure = Date.now() - this.lastConnectionFailureTimestamp;
    if (this.lastConnectionFailureTimestamp > 0 && timeSinceLastFailure < RECONNECT_BACKOFF_MS) {
      throw new ChromaUnavailableError(`chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BACKOFF_MS - timeSinceLastFailure) / 1000)}s remaining)`);
    }

    if (this.connecting) {
      await this.connecting;
      return;
    }

    this.connecting = this.connectInternal();
    try {
      await this.connecting;
    } catch (error) {
      if (error instanceof ChromaMcpConnectionCancelledError) {
        logger.debug('CHROMA_MCP', 'Connection attempt cancelled during shutdown');
        throw error;
      }
      this.lastConnectionFailureTimestamp = Date.now();
      if (error instanceof Error) {
        logger.error('CHROMA_MCP', 'Connection attempt failed', {}, error);

View on GitHub (pinned to d768ba3643)

Solutions

  1. Wait the number of seconds shown in the message, then retry — the backoff is short (10s).
  2. Address the root connection failure (see the preceding CHROMA_MCP 'Connection attempt failed' log entry): install uvx, fix the data dir, free resources.
  3. If vector search is optional, catch ChromaUnavailableError and fall back to FTS/local search.
  4. Reduce parallel search fan-out so fewer callers pile up inside the backoff window.

Example fix

// before
const results = await chroma.search(query); // throws during backoff

// after — treat unavailable as non-fatal
catch (e) {
  if (e instanceof ChromaUnavailableError) {
    return await ftsSearch(query); // fallback
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

// Backoff-aware caller: don't even attempt within the backoff window.
function backoffSecondsRemaining(lastFailAt: number, backoffMs = 10_000): number {
  if (lastFailAt <= 0) return 0;
  const remaining = backoffMs - (Date.now() - lastFailAt);
  return remaining > 0 ? Math.ceil(remaining / 1000) : 0;
}

Type guard

import { ChromaUnavailableError } from '../worker/search/errors.js';

function isChromaUnavailable(e: unknown): boolean {
  return e instanceof ChromaUnavailableError;
}

Try / catch

import { ChromaUnavailableError } from '../worker/search/errors.js';

async function searchWithFallback(q: string) {
  try {
    return await chroma.search(q);
  } catch (e) {
    if (e instanceof ChromaUnavailableError) {
      return await ftsSearch(q); // vector search optional -> degrade
    }
    throw e;
  }
}

Prevention

When it happens

Trigger: Any chroma operation (vector search/upsert via the manager) is attempted within 10 seconds of the previous connection failure. lastConnectionFailureTimestamp was set because connectInternal() rejected (uvx missing, prewarm timeout, MCP handshake failure, etc.).

Common situations: chroma-mcp repeatedly fails to start (uvx/uv not installed, model download slow, port/stderr issues) and every subsequent search within 10s hits the backoff; a transient failure just occurred and a burst of parallel searches all see backoff; the worker retries too aggressively.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/1283be21413780b6. Report an issue: GitHub.