Skip to content

search --fts mislabels FTS5 query-syntax errors as "database error:" and exits with DbError = 3tokio::spawn with --fts returns no such column: tokio with no hint that : is a column qualifier #187

Description

@Widthdom

Summary

search --fts sends the user's query directly into SQLite FTS5's MATCH clause. When FTS5 rejects the query (syntax error, non-existent column, unterminated string, etc.), the resulting SqliteException is wrapped by cdidx's CLI error handler as Error: database error: ... and exits with code 3 = DatabaseError. Neither of those is correct — the failure is a query-syntax error, not a database problem. AI agents and CI scripts that branch on exit 3 treat this as "DB is corrupt, rebuild" and take the wrong recovery path.

Worst offender: cdidx search "tokio::spawn" --fts returns no such column: tokio because FTS5 treats the first : as a column qualifier. Users pasting real C++/Rust/Scala/PHP identifiers with :: hit this by accident, read "database error," and go debug the DB instead of the query.

Repro

curl -fsSL https://raw.githubusercontent.com/Widthdom/CodeIndex/main/install.sh | bash
CDIDX=/root/.local/bin/cdidx

mkdir -p /tmp/srcs && cd /tmp/srcs
curl -fsSL -o tokio.tar.gz https://codeload.github.com/tokio-rs/tokio/tar.gz/refs/heads/master
tar xzf tokio.tar.gz
"$CDIDX" /tmp/srcs/tokio-master --db /tmp/tokio.db
# The most confusing one: a plausible Rust/C++ identifier query
"$CDIDX" search "tokio::spawn" --db /tmp/tokio.db --fts
# Error: database error: SQLite Error 1: 'no such column: tokio'.
# exit=3

# FTS5 syntax error
"$CDIDX" search "AND OR" --db /tmp/tokio.db --fts
# Error: database error: SQLite Error 1: 'fts5: syntax error near "AND"'.
# exit=3

# Unterminated quote
"$CDIDX" search 'foo"bar' --db /tmp/tokio.db --fts
# Error: database error: SQLite Error 1: 'unterminated string'.
# exit=3

# Intentional column qualifier to a non-existent column
"$CDIDX" search "not_a_col:foo" --db /tmp/tokio.db --fts
# Error: database error: SQLite Error 1: 'no such column: not_a_col'.
# exit=3

All four are query-authoring mistakes (bad FTS5 input). None are database problems.

The default (literal-safe) path is healthy

"$CDIDX" search "tokio::spawn" --db /tmp/tokio.db --count   # → 198, exit 0
"$CDIDX" search 'foo"bar'      --db /tmp/tokio.db --count   # → 9,  exit 0
"$CDIDX" search 'データ'        --db /tmp/tokio.db --count   # → 0,  exit 0

The issue is specifically in the --fts path's error mapping.

Suspected root cause (from the source)

Likely in src/CodeIndex/Database/DbSearchReader.cs: when an FTS5 MATCH throws SqliteException, it propagates to the top-level CLI error handler, which prefixes every SqliteException with "Error: database error: " and exits with CommandExitCodes.DatabaseError = 3. There's no distinction between "SQLite had a schema/IO problem" and "SQLite's FTS5 parser rejected the user's query."

Why it matters

  • Misleading user-facing text. "database error" suggests running --rebuild or checking DB file permissions. Neither fixes the real problem (change the query or drop --fts).
  • Wrong exit code for scripts. CommandExitCodes.DbError = 3 is documented as "database error — run --rebuild". AI agents following that contract take a wasteful recovery path.
  • No hint about the : column-qualifier semantics. A Rust/C++/Scala/PHP user who pastes foo::bar with --fts has no way to know why they got "no such column: foo" — they never wrote a column qualifier.

Suggested direction

  1. Catch the SQLite exception specifically inside the FTS5 MATCH path and reclassify:

    try {
        // FTS5 MATCH query
    } catch (SqliteException ex) when (IsFtsSyntaxError(ex)) {
        throw new FtsQuerySyntaxException(ex.Message, ex);
    }

    where IsFtsSyntaxError checks for the known marker strings: fts5: syntax error, unterminated string, no such column.

  2. CLI error handler: when the exception is an FtsQuerySyntaxException, print Error: FTS5 query syntax: <message> instead of the DB-error prefix, and exit with CommandExitCodes.UsageError = 1.

  3. Add a targeted hint for the no such column case:

    Error: FTS5 query syntax: no such column: tokio
    Hint: '--fts' interprets the first ':' in a token as an FTS5 column qualifier.
          If ':' is part of the identifier, drop '--fts' to use default literal-safe search.
    
  4. Document in --help and README: "--fts passes raw FTS5 syntax; the query must follow FTS5 rules (column qualifiers with :, AND/OR/NEAR/NOT operators, quoted phrases). Use the default search (without --fts) for literal text."

Scope

  • src/CodeIndex/Database/DbSearchReader.cs — classify FTS5 MATCH exceptions.
  • src/CodeIndex/Cli/QueryCommandRunner.cs (or the central error handler) — dedicated exit code + message for FTS syntax errors.
  • src/CodeIndex/Cli/CommandExitCodes.cs — possibly add a dedicated FtsSyntaxError or reuse UsageError = 1.
  • tests/CodeIndex.Tests/QueryCommandRunnerTests.cs — regression:
    • search "X::Y" --fts → exit != 3, message mentions FTS5 column qualifier
    • search "AND OR" --fts → exit != 3, message mentions FTS5 syntax
    • search "foo" --fts on a valid token → exit 0, results returned

Related

Environment

  • cdidx: v1.10.0 (installed via install.sh).
  • Any indexed project reproduces. tokio-rs/tokio@master used above.
  • Platform: linux-x64 container.
  • Filed from a cloud Claude Code session per CLOUD_BOOTSTRAP_PROMPT.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions