{"record":{"id":"1283be21413780b6","repo":"thedotmack/claude-mem","slug":"chroma-mcp-connection-in-backoff-math-ceil-rec","errorCode":null,"errorMessage":"chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BACKOFF_MS - timeSinceLastFailure) / 1000)}s remaining)","messagePattern":"chroma-mcp connection in backoff \\((.+?)s remaining\\)","errorType":"exception","errorClass":"ChromaUnavailableError","httpStatus":null,"severity":"warning","filePath":"src/services/sync/ChromaMcpManager.ts","lineNumber":125,"sourceCode":"  }\n\n  static getInstance(): ChromaMcpManager {\n    if (!ChromaMcpManager.instance) {\n      ChromaMcpManager.instance = new ChromaMcpManager();\n    }\n    return ChromaMcpManager.instance;\n  }\n\n  private async ensureConnected(): Promise<void> {\n    await this.waitForUnexpectedCloseCleanup();\n\n    if (this.connected && this.client) {\n      return;\n    }\n\n    const timeSinceLastFailure = Date.now() - this.lastConnectionFailureTimestamp;\n    if (this.lastConnectionFailureTimestamp > 0 && timeSinceLastFailure < RECONNECT_BACKOFF_MS) {\n      throw new ChromaUnavailableError(`chroma-mcp connection in backoff (${Math.ceil((RECONNECT_BACKOFF_MS - timeSinceLastFailure) / 1000)}s remaining)`);\n    }\n\n    if (this.connecting) {\n      await this.connecting;\n      return;\n    }\n\n    this.connecting = this.connectInternal();\n    try {\n      await this.connecting;\n    } catch (error) {\n      if (error instanceof ChromaMcpConnectionCancelledError) {\n        logger.debug('CHROMA_MCP', 'Connection attempt cancelled during shutdown');\n        throw error;\n      }\n      this.lastConnectionFailureTimestamp = Date.now();\n      if (error instanceof Error) {\n        logger.error('CHROMA_MCP', 'Connection attempt failed', {}, error);","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d768ba364302d12b76e69e4f021f0bb1d2d50ed6/src/services/sync/ChromaMcpManager.ts#L107-L143","documentation":"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.","triggerScenarios":"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.).","commonSituations":"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.","solutions":["Wait the number of seconds shown in the message, then retry — the backoff is short (10s).","Address the root connection failure (see the preceding CHROMA_MCP 'Connection attempt failed' log entry): install uvx, fix the data dir, free resources.","If vector search is optional, catch ChromaUnavailableError and fall back to FTS/local search.","Reduce parallel search fan-out so fewer callers pile up inside the backoff window."],"exampleFix":"// before\nconst results = await chroma.search(query); // throws during backoff\n\n// after — treat unavailable as non-fatal\ncatch (e) {\n  if (e instanceof ChromaUnavailableError) {\n    return await ftsSearch(query); // fallback\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// Backoff-aware caller: don't even attempt within the backoff window.\nfunction backoffSecondsRemaining(lastFailAt: number, backoffMs = 10_000): number {\n  if (lastFailAt <= 0) return 0;\n  const remaining = backoffMs - (Date.now() - lastFailAt);\n  return remaining > 0 ? Math.ceil(remaining / 1000) : 0;\n}","typeGuard":"import { ChromaUnavailableError } from '../worker/search/errors.js';\n\nfunction isChromaUnavailable(e: unknown): boolean {\n  return e instanceof ChromaUnavailableError;\n}","tryCatchPattern":"import { ChromaUnavailableError } from '../worker/search/errors.js';\n\nasync function searchWithFallback(q: string) {\n  try {\n    return await chroma.search(q);\n  } catch (e) {\n    if (e instanceof ChromaUnavailableError) {\n      return await ftsSearch(q); // vector search optional -> degrade\n    }\n    throw e;\n  }\n}","preventionTips":["Treat ChromaUnavailableError as non-fatal and fall back to FTS/local search.","Fix the underlying connection failure (see CHROMA_MCP logs) rather than retrying blindly.","Space out parallel searches to avoid piling up inside the 10s backoff window."],"tags":["chroma","backoff","transient","vector-search","rate-limiting"],"backgroundTag":null,"analyzedSha":"d768ba364302d12b76e69e4f021f0bb1d2d50ed6","analyzedAt":"2026-08-12T23:52:55.241Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}