Files
Jiangzhou 9fd2e7470a fix(search): surface daemon-side tracebacks and reject NULL KNN distances (#271)
Issue #270 reported `ccc search --path` crashing with `TypeError:
unsupported operand type(s) for *: 'NoneType' and 'NoneType'`. The crash
could not be reproduced, and the reported root cause does not hold:
`vec_distance_L2` never returns NULL, it raises (verified against
sqlite-vec 0.1.6-0.1.9, SQLite 3.46/3.53, multi-chunk tables and
re-index churn). What the report did expose is that the failure was
undiagnosable.

Two gaps, both fixed here:

- The daemon's search handler discarded the traceback
  (`ErrorResponse(message=str(e))`), so the reporter saw only the
  client's re-raise frames. It now sends `traceback.format_exc()` and
  logs the exception; `_dispatch` does the same. On the client, the
  `raise RuntimeError(f"Daemon error: ...")` pattern was duplicated at
  five sites and only `doctor()` appended `resp.traceback` -- the search
  path, which the reporter came through, dropped it. Consolidated into
  one `_daemon_error()` helper used by all five.

- `_knn_query` now rejects rows with a NULL distance, raising a specific
  error naming the query shape and offending file instead of dying in
  `_l2_to_score`. `distance` is a hidden vec0 column that sqlite-vec
  populates only under the KNN query plan and returns NULL for on a full
  scan, so a NULL means the plan we asked for is not the plan we got.
  The guard lives in `_knn_query` rather than the caller because the
  multi-language merge path sorts on `r[5]` in `heapq.nsmallest`, where
  a NULL would fail on `None < float` before any caller-side check ran.
  `_full_scan_query` needs no guard: it computes `vec_distance_L2(...)`
  itself, which works under any plan and raises rather than returning
  NULL on bad input.

The new tests build a real in-memory vec0 table with the indexer's exact
DDL (no embedding model, ~0.2s) and cover both that `--path` filtering
yields usable distances and that the bare-`distance`-under-full-scan
shape is the one that does not.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 16:10:51 -07:00
..