thedotmack/claude-mem · warning

FTS fallback failed for observations

Error message

FTS fallback failed for observations

What it means

Same FTS fallback guard as the timeline path, but for the dedicated observations search: when the primary observation search returns nothing, the FTS fallback searchObservations(query, options) throwing is caught and logged, then the handler proceeds with an empty result response.

Solutions

  1. Read ftsError in the log to identify the SQLite-level cause.
  2. Rebuild the FTS index (rebuild command or INSERT INTO ...('rebuild')).
  3. Confirm the FTS extension is enabled in the bundled SQLite build.
  4. Empty results are still returned gracefully — retry after repairing the index.
Defensive patterns

Strategy: fallback

Validate before calling

if (!query || query.trim().length === 0) return emptyResponse();

Try / catch

try {
  results = sessionSearch.searchObservations(query, options);
} catch (ftsError) {
  logger.warn('FTS fallback failed for observations', { ftsError });
  results = [];
}

Prevention

When it happens

Trigger: searchObservations handler's primary path yields zero results and the FTS fallback this.sessionSearch.searchObservations(query, options) throws — e.g. FTS index corruption, malformed query tokens, or DB errors.

Common situations: FTS5 table unavailable after migration; database locked or disk issues; special characters in queries breaking tokenization upstream.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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

Appendix: source

Thrown at src/services/worker/SearchManager.ts:942

      try {
        const limit = options.limit || 20;
        results = await this.hybridSemanticHydrate(query, 'observation', options.project, options.platformSource, (ids) =>
          this.sessionStore.getObservationsByIds(ids, { orderBy: 'relevance', limit, project: options.project, platformSource: options.platformSource })
        );
      } catch (chromaError) {
        const errorObject = chromaError instanceof Error ? chromaError : new Error(String(chromaError));
        logger.error('WORKER', 'Chroma search failed for observations, falling back to FTS', {}, errorObject);
      }
    }

    if (results.length === 0) {
      try {
        const ftsResults = this.sessionSearch.searchObservations(query, options);
        if (ftsResults.length > 0) {
          results = ftsResults;
        }
      } catch (ftsError) {
        logger.warn('SEARCH', 'FTS fallback failed for observations', {}, ftsError instanceof Error ? ftsError : undefined);
      }
    }

    if (results.length === 0) {
      return {
        content: [{
          type: 'text' as const,
          text: `No observations found matching "${query}"`
        }]
      };
    }

    const header = `Found ${results.length} observation(s) matching "${query}"\n\n${this.formatter.formatTableHeader()}`;
    const formattedResults = results.map((obs, i) => this.formatter.formatObservationIndex(obs, i));

    return {
      content: [{
        type: 'text' as const,

View on GitHub (pinned to d8bc9755e7)