fix(index): clear the legacy embeddings table on reset and deindex - #688
Conversation
reset()'s docstring warns "leaving stale rows here means semantic search can return orphaned hits after a reindex" and deindex()'s docstring promises to remove "the embedding row for any kind," but neither ever touched the legacy embeddings table alongside embedding_index. every artifact deletion or full reindex leaked a stale vector row that never gets cleaned up. search_embeddings (plural, the reader of the legacy table) currently has no callers in src/, so this isn't live search pollution today - but health._check_orphan_embeddings explicitly checks both tables, so a leaked row permanently trips the orphan_embedding fsck warning with no way to clear it via the documented remedy (reindexing), and grows state.db unbounded over a kb's lifetime. add the missing DELETE FROM embeddings to reset()'s executescript, and the missing per-row delete to deindex(), matching the existing embedding_index handling in both. this exact defect and fix were previously submitted as vouchdev#543 (fix(index): clear the legacy embeddings table on reset and deindex), but that PR was closed unmerged for going stale against a fast-moving test branch, not for anything wrong with the change; the maintainer's closing comment explicitly invited a fresh PR. Fixes vouchdev#687
…beddings-cleanup # Conflicts: # CHANGELOG.md
|
clean, and the honesty about scope is the right call — the correction you posted on the issue narrowing this from "live search pollution" to "a permanently stuck checked the upgrade path, since a bare placing the the two tests assert through no objections. |
What changed
index_db.reset()andindex_db.deindex()now clear the legacyembeddingstable alongsideembedding_index, matching what bothfunctions' own docstrings already claim to do.
Why
reset()'s docstring: "Leaving stale rows here means semantic search canreturn orphaned hits after a reindex."
deindex()'s docstring: removes"the embedding row for any kind." Neither function's
executescript/executecalls actually touched the legacyembeddingstable — onlyembedding_index. Confirmed with a repro: indexing a row viaindex_embedding(), then callingdeindex()(orreset()), leaves therow in
embeddingsuntouched.search_embeddings(plural, the function that reads the legacy table)currently has zero callers anywhere in
src/— a pre-existing comment intests/test_index_db_embeddings.pyalready notes this — so this isn'tlive search-result pollution today. The concrete, verified-live impact is
narrower:
health._check_orphan_embeddingsexplicitly checks both thelegacy
embeddingstable andembedding_index, so a leaked rowpermanently trips the
orphan_embeddingfsck warning with no way toclear it via the documented remedy (reindexing), and
state.dbgrowsunbounded with dead rows over a KB's lifetime. (I initially overstated the
live-search angle in the linked issue and posted a correction there.)
Fixes #687
What might break
Nothing for users with an existing
.vouch/directory — no on-diskshape,
kb.*method, or object model change; this is purelystate.db-internal cleanup (a derived/rebuildable index, nevercommitted). Behaviorally:
vouch index(reset) and artifact deletion nowactually remove the row from both embedding tables instead of one.
VEP
Not applicable — no object model,
kb.*method, on-disk layout, bundleformat, or audit-log shape change.
state.dbis a derived, rebuildablecache, not part of the on-disk artifact format.
Prior art
This exact defect and fix were previously submitted as #543
(
fix(index): clear the legacy embeddings table on reset and deindex)but closed unmerged on 2026-07-29 purely for going stale against
test(a
CHANGELOG.mdconflict) — the maintainer's closing comment explicitlysaid this wasn't a judgment on the change. Re-verified independently
against current
testHEAD: the gap is still live in both functions.Tests
make check-equivalent: ruff clean (src+tests); mypyclean on
index_db.py; all 36tests/test_index_db_embeddings.pycases pass (34 pre-existing + 2 new);
tests/test_delete.pyandtests/test_health.py(both exercisedeindex/orphan_embedding)pass with no regressions
test_deindex_removes_the_legacy_embeddings_row,test_reset_clears_the_legacy_embeddings_tableCHANGELOG.mdupdated under## [Unreleased]