…fill
A follow-up interrupted-resume shape of NousResearch#72716 (reported on the issue,
2026-08-17): fts_rebuild markers set while messages_fts /
messages_fts_trigram are v22-shaped single-column content vtables. The
resume path's _ensure_fts_schema uses CREATE VIRTUAL TABLE IF NOT
EXISTS, which silently ACCEPTS the wrong-shaped table, and the very
first fts_rebuild_step INSERT then fails with "no column named
tool_name" - the step loop treats every OperationalError as retryable,
so optimize-storage hangs forever on a 3-row database.
Add _drop_mismatched_fts_vtables(): probe each vtable's column set,
drop any that lack the v23 columns, and reset fts_rebuild_progress to
0 (rows claimed under the wrong shape may be missing even though the
marker advanced). It runs after the legacy/pending branch dispatch, so
every path - including the legacy+pending combination that previously
skipped both branches - reaches the backfill with correctly-shaped
tables; the markers then drive a full re-population.
Fixes NousResearch#72716 (interrupted-resume shape; the original empty-stamp
settle was fixed by NousResearch#76832)
What does this PR do?
Fixes the interrupted-resume shape reported as a follow-up on #72716 (comment, 2026-08-17):
fts_rebuild_high_water/fts_rebuild_progressmarkers are set whilemessages_fts/messages_fts_trigramexist as v22-shaped single-columncontentvtables._ensure_fts_schema's DDL isCREATE VIRTUAL TABLE IF NOT EXISTS, which silently accepts the wrong-shaped table — the firstfts_rebuild_stepINSERT then fails withtable messages_fts has no column named tool_name, and because the step loop treats everyOperationalErroras retryable,optimize-storagehangs forever (reproduced: infinite retry on a 3-row database; the reporter hit it on a real DB withhigh_water=60993).This PR adds
_drop_mismatched_fts_vtables()— probe each FTS vtable's column set (viaSELECT ... LIMIT 0description), drop any that lack the v23 columns, and resetfts_rebuild_progressto 0 (rows claimed under the wrong shape may be missing even though the marker advanced). The guard runs after the legacy/pending branch dispatch, so every path reaches the backfill with correctly-shaped tables — including thelegacy and pendingcombination that previously skipped both branches and fell straight into the dead loop. The surviving markers drive a full re-population, so a resume now completes instead of hanging: search for historical rows is restored and the markers clear.The original empty-stamp settle defect from #72716 was already fixed by #76832 (merged); this covers the shape that fix missed.
Related Issue
Fixes #72716 (interrupted-resume shape)
Type of Change
Changes Made
hermes_state_search.py: new_drop_mismatched_fts_vtables()— column-set probe + drop + progress reset for both vtables; called after the legacy/pending dispatch inoptimize_fts_storage()followed by a re-ensure, with docstrings documenting the IF-NOT-EXISTS blind spot and the [Bug]: optimize-storage can stamp empty FTS after interrupted demote (permanent search loss) #72716 follow-up shapetests/test_hermes_state.py: newtest_optimize_resume_rebuilds_wrong_shaped_vtables— builds the reporter's exact durable state (single-column vtables + markers + advanced progress), asserts the v23 INSERT fails on the wrong shape (sanity), then asserts the optimize run completes, the vtable comes back with the v23 column set, the index is fully populated (docsize count == messages count), historical search works, and the markers clearHow to Test
python -m pytest tests/test_hermes_state.py::TestFTSExternalContentMigration -q— Observed result: 15 passed (the fix(state): do not stamp empty FTS after interrupted optimize-storage demote (salvage #72717) #76832 regression tests continue to pass: the shape guard is a no-op on correctly-shaped tables).python -m pytest tests/test_hermes_state.py tests/test_state_db_malformed_repair.py tests/test_journal_mode_config.py -q— Observed result: 272 passed, 1 failed — the single failure (TestFTS5Search::test_search_projection_skips_context_enrichment_queries) reproduces identically with this change stashed (pre-existing local environment noise, not a regression).optimize_fts_storageon that state hangs in the retry loop (verified locally: the backfill step returns "more" forever with progress frozen); post-fix it completes in ~4s with search restored.Checklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
For New Skills
N/A
Screenshots / Logs