thedotmack/claude-mem · warning

FTS fallback failed for timeline

Error message

FTS fallback failed for timeline

What it means

Logged when the FTS (full-text search) fallback used to backfill timeline results throws. The primary timeline search returned no results, and the fallback call to sessionSearch.searchObservations failed; the failure is swallowed and the flow continues to the empty-results branch.

Solutions

  1. Check the ftsError detail in the log payload to find the underlying SQLite/FTS failure.
  2. Rebuild the FTS index for the sessions database.
  3. Verify database file integrity and that no other process holds a write lock.
  4. The tool still returns a valid empty-results response — treat this as degraded search, not data loss.

Example fix

// after migration
INSERT INTO observations(observations) VALUES('rebuild');
Defensive patterns

Strategy: fallback

Validate before calling

// verify FTS index health before searching
const ok = db.prepare("SELECT count(*) FROM observations WHERE observations MATCH 'test'").get();

Try / catch

try {
  results = sessionSearch.searchObservations(query, opts);
} catch (e) {
  logger.warn('FTS fallback failed', { error: e });
  results = []; // degrade gracefully to empty results
}

Prevention

When it happens

Trigger: searchTimeline's primary search yields nothing, then the fallback this.sessionSearch.searchObservations(query, {project, platformSource, limit:1}) throws (e.g. corrupt/missing FTS index, SQL error).

Common situations: SQLite FTS table missing or corrupted after upgrade/migration; database locked by another process; query syntax issues escaping the FTS layer.

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/3ef392b55754101b. Report an issue: GitHub.

Appendix: source

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

      if (this.chromaSync) {
        logger.debug('SEARCH', 'Using hybrid semantic search for timeline query', {});
        try {
          results = await this.searchChromaForTimeline(query, project, platformSource);
        } catch (chromaError) {
          const errorObject = chromaError instanceof Error ? chromaError : new Error(String(chromaError));
          logger.error('WORKER', 'Chroma search failed for timeline, continuing without semantic results', {}, errorObject);
        }
      }

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

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

      const topResult = results[0];
      anchorId = topResult.id;
      anchorEpoch = topResult.created_at_epoch;
      logger.debug('SEARCH', 'Query mode: Using observation as timeline anchor', { observationId: topResult.id });
      timelineData = this.sessionStore.getTimelineAroundObservation(topResult.id, topResult.created_at_epoch, depthBefore, depthAfter, project, platformSource);
    }

View on GitHub (pinned to d8bc9755e7)