thedotmack/claude-mem · error

FTS5 observation search failed

Error message

FTS5 observation search failed

What it means

An FTS5 MATCH query against the observations full-text index threw. SessionSearch logs this warning and rethrows, so callers of searchObservations() receive the raw SQLite error. Usual root causes: the FTS table was never created (the companion 'FTS5 table creation failed' warning fired at startup), a corrupt FTS index, or SQLITE_BUSY from a concurrent writer.

Source

Thrown at src/services/sqlite/SessionSearch.ts:292

      const sql = `
        SELECT o.*, o.discovery_tokens
        FROM observations o
        JOIN observations_fts ON observations_fts.rowid = o.id
        WHERE observations_fts MATCH ?
        ${filterClause ? 'AND ' + filterClause : ''}
        ${orderClause}
        LIMIT ? OFFSET ?
      `;

      const escapedQuery = '"' + query.replace(/"/g, '""') + '"';
      params.unshift(escapedQuery);
      params.push(limit, offset);

      try {
        return this.db.prepare(sql).all(...params) as ObservationSearchResult[];
      } catch (error) {
        logger.warn('DB', 'FTS5 observation search failed', {}, error instanceof Error ? error : undefined);
        throw error;
      }
    }

    logger.warn('DB', 'Text search unavailable: ChromaDB disabled and FTS5 not available');
    return [];
  }

  searchSessions(query: string | undefined, options: SearchOptions = {}): SessionSummarySearchResult[] {
    const params: any[] = [];
    const { limit = 50, offset = 0, orderBy = 'relevance', ...filters } = options;

    if (!query) {
      const filterOptions = { ...filters };
      delete filterOptions.type;
      const filterClause = this.buildFilterClause(filterOptions, params, 's');
      if (!filterClause) {
        throw new AppError(SessionSearch.MISSING_SEARCH_INPUT_MESSAGE, 400, 'INVALID_SEARCH_REQUEST');

View on GitHub (pinned to e2d1df569a)

Solutions

  1. Scan the same log for the earlier 'FTS5 table creation failed' / 'FTS5 not available' warnings — they are the usual root cause
  2. Confirm the FTS table exists: SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%fts%';
  3. If missing or corrupt, drop the FTS tables plus shadow tables and restart so initializeFTS() recreates them
  4. If SQLITE_BUSY: reduce concurrent writers or raise busy_timeout before searching
  5. Wrap callers with a fallback to ChromaDB or LIKE-based search on failure

Example fix

// before
const rows = search.searchObservations(q, { limit: 20 });

// after — degrade to non-FTS search when the MATCH fails
let rows: ObservationSearchResult[];
try {
  rows = search.searchObservations(q, { limit: 20 });
} catch (err) {
  logger.warn('DB', 'FTS observation search failed; falling back', {}, err instanceof Error ? err : undefined);
  rows = search.searchObservations(undefined, { limit: 20 }); // undefined query skips the FTS path
}
Defensive patterns

Strategy: try-catch

Validate before calling

const hasFts = !!db
  .prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE '%fts%'`)
  .get();
if (!hasFts) {
  // skip the text-search path; use ChromaDB or LIKE directly
}

Try / catch

try {
  rows = search.searchObservations(q, { limit });
} catch (err) {
  logger.warn('DB', 'FTS observation search failed; using fallback', {}, err instanceof Error ? err : undefined);
  rows = search.searchObservations(undefined, { limit }); // undefined query skips FTS
}

Prevention

When it happens

Trigger: Calling searchObservations(query, ...) with a non-empty query while the FTS table or its triggers are missing/corrupt despite _fts5Available being true; a writer holding an exclusive lock while the MATCH runs; the FTS table dropped manually after init.

Common situations: FTS creation failed silently in an earlier session; two workers sharing one DB file with one writing while the other searches; a DB restored from a partial backup missing shadow tables.

Related errors


AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20). Data as JSON: /api/errors/a14812be9550df4d. Report an issue: GitHub.