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
- Scan the same log for the earlier 'FTS5 table creation failed' / 'FTS5 not available' warnings — they are the usual root cause
- Confirm the FTS table exists: SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%fts%';
- If missing or corrupt, drop the FTS tables plus shadow tables and restart so initializeFTS() recreates them
- If SQLITE_BUSY: reduce concurrent writers or raise busy_timeout before searching
- 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
- Confirm the 'FTS5 tables created successfully' log line before advertising text-search features
- Avoid concurrent exclusive writers or set busy_timeout so MATCH queries do not hit SQLITE_BUSY
- Never drop FTS tables manually without also resetting the availability flag
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
- FTS5 session search failed
- FTS5 table creation failed — search will use ChromaDB and LI
- FTS5 not available — user_prompts_fts skipped (search uses C
- Invalid CLAUDE_MEM_QUEUE_ENGINE=${raw}; expected sqlite or b
- cloud sync canonical payload: ${name} must be a non-negative
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/a14812be9550df4d.
Report an issue: GitHub.