thedotmack/claude-mem · error · ChromaUnavailableError

Chroma query failed

Error message

Chroma query failed: ${errorObj.message}

What it means

The search orchestrator wraps any failure from the Chroma (vector) search strategy in a ChromaUnavailableError carrying the original message, so callers can distinguish 'Chroma is down/broken' from 'no results'. Note the orchestrator already falls back to SQLite only when Chroma returns zero matches — a thrown error propagates instead of falling back.

Solutions

  1. Check that the Chroma service is running and reachable (process, port, health endpoint)
  2. Catch ChromaUnavailableError at the call site and fall back to the SQLite strategy, mirroring the zero-match fallback
  3. Inspect the cause property (the wrapped original error) for the real failure — connection refused vs HTTP error vs embedding failure
  4. Verify the chroma index and embedding configuration are intact and dimensionally consistent

Example fix

// before
const results = await orchestrator.search(options); // throws if Chroma errors
// after
let results;
try {
  results = await orchestrator.search(options);
} catch (err) {
  if (err instanceof ChromaUnavailableError) {
    results = await sqliteStrategy.search(options); // graceful degradation
  } else {
    throw err;
  }
}
Defensive patterns

Strategy: fallback

Validate before calling

import { existsSync } from 'fs';
// quick health check before issuing vector searches:
const chromaUp = existsSync(chromaDataDir); // plus a real ping/health call to the Chroma endpoint in production

Type guard

function isChromaUnavailable(err: unknown): err is ChromaUnavailableError {
  return err instanceof ChromaUnavailableError;
}

Try / catch

try {
  results = await orchestrator.search(options);
} catch (err) {
  if (isChromaUnavailable(err)) {
    logger.warn('Chroma unavailable, falling back to SQLite', { cause: err.cause?.message });
    results = await sqliteStrategy.search(options);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Chroma HTTP query fails: Chroma server not running, connection refused, malformed query embedding, Chroma returning 4xx/5xx, or any non-Error thrown value in the chroma search path.

Common situations: Chroma subprocess (managed via uv/python) failed to start or crashed; port mismatch or stale CHROMA endpoint config after an upgrade; empty/corrupt chroma index directory; embedding dimension mismatch between index and query model.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at src/services/worker/search/SearchOrchestrator.ts:68

    options: NormalizedParams
  ): Promise<StrategySearchResult> {
    if (!options.query) {
      logger.debug('SEARCH', 'Orchestrator: Filter-only query, using SQLite', {});
      return await this.sqliteStrategy.search(options);
    }

    if (this.chromaStrategy) {
      logger.debug('SEARCH', 'Orchestrator: Using Chroma semantic search', {});
      try {
        const chromaResult = await this.chromaStrategy.search(options);
        if (this.isEmptyResult(chromaResult)) {
          logger.debug('SEARCH', 'Orchestrator: Chroma search returned zero matches; falling back to SQLite', {});
          return await this.sqliteStrategy.search(options);
        }
        return chromaResult;
      } catch (error) {
        const errorObj = error instanceof Error ? error : new Error(String(error));
        throw new ChromaUnavailableError(
          `Chroma query failed: ${errorObj.message}`,
          errorObj
        );
      }
    }

    logger.debug('SEARCH', 'Orchestrator: Chroma not configured', {});
    return {
      results: { observations: [], sessions: [], prompts: [] },
      usedChroma: false,
      strategy: 'sqlite'
    };
  }

  private isEmptyResult(result: StrategySearchResult): boolean {
    return result.results.observations.length === 0
      && result.results.sessions.length === 0
      && result.results.prompts.length === 0;

View on GitHub (pinned to d8bc9755e7)