Skip to content

fix(search): surface daemon tracebacks and reject NULL KNN distances - #271

Merged
georgeh0 merged 1 commit into
mainfrom
claude/github-issue-270-58187e
Aug 6, 2026
Merged

fix(search): surface daemon tracebacks and reject NULL KNN distances#271
georgeh0 merged 1 commit into
mainfrom
claude/github-issue-270-58187e

Conversation

@georgeh0

@georgeh0 georgeh0 commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Daemon search failures now carry the daemon-side traceback to the client: _search_with_wait and _dispatch populate ErrorResponse.traceback (and log the exception), and the client's five duplicated Daemon error: raise sites are consolidated into one _daemon_error() helper that appends the traceback everywhere, not just in doctor(). Motivated by ccc search --path crashes the daemon: unsupported operand type(s) for *: 'NoneType' and 'NoneType' #270, which arrived with no frame pointing at the failing code.
  • _knn_query rejects rows with a NULL distance with a specific, reportable error instead of letting the NULL reach _l2_to_score and die as TypeError: unsupported operand type(s) for *: 'NoneType' and 'NoneType'. distance is a hidden vec0 column populated only under the KNN query plan (NULL on a full scan), so a NULL means the plan we asked for is not the plan we got. _full_scan_query needs no guard — it computes vec_distance_L2(...) itself, which works under any plan and raises rather than returning NULL.
  • New tests/test_query_filters.py runs against a real in-memory vec0 table using the indexer's exact DDL (no embedding model, ~0.2s): --path and --lang+--path full scans yield usable distances (the claim in ccc search --path crashes the daemon: unsupported operand type(s) for *: 'NoneType' and 'NoneType' #270 — not reproducible), and the bare-distance-under-full-scan shape is pinned as the one that yields NULLs.

Note: this does not close #270 — the crash could not be reproduced (see the issue thread for the analysis); this makes any recurrence self-diagnosing.

Test plan

CI, plus new tests: tests/test_query_filters.py (vec0 SQL layer), test_daemon_error_carries_daemon_side_traceback (client), test_search_failure_reports_daemon_side_traceback (daemon).

🤖 Generated with Claude Code

…nces

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>
@georgeh0
georgeh0 merged commit 9fd2e74 into main Aug 6, 2026
4 checks passed
@georgeh0
georgeh0 deleted the claude/github-issue-270-58187e branch August 6, 2026 23:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ccc search --path crashes the daemon: unsupported operand type(s) for *: 'NoneType' and 'NoneType'

1 participant