From 11e76f4e019d2b1cd4b7f5468a30f1bbcb3c2d08 Mon Sep 17 00:00:00 2001 From: Enough1122 Date: Sat, 18 Jul 2026 20:10:03 +0800 Subject: [PATCH] fix(state): probe FTS5 read path in _db_opens_cleanly so partial index corruption is detected (#66724) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hermes sessions repair --check-only` opens cleanly on state.db files with partial FTS5 index corruption — base tables read fine, the rolled-back write probe from #50502 succeeds, and `PRAGMA integrity_check` returns "ok". But every session_search / /resume title resolution / feature backed by MATCH / snippet / rank queries errors out with `database disk image is malformed` because internal shadow-table segments are bad. The official repair tool then gives false confidence. Add a representative FTS5 read probe against both `messages_fts` and `messages_fts_trigram` (the latter backs title resolution). Empty MATCH strings are accepted by every FTS5 index without requiring populated content, so the probe is safe on a freshly-init'd DB; missing-table / missing-column errors fall through to the existing "not yet a populated DB" branch, matching the write-probe's behaviour. Any other OperationalError is surfaced as the check reason, which sends `hermes sessions repair` to its existing FTS 'rebuild' path (repair_state_db_schema, line 616). Single-file change in hermes_state.py::_db_opens_cleanly. No public API change. No new imports. Fixes #66724. --- hermes_state.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/hermes_state.py b/hermes_state.py index 7978eb94e2f8..67343bc47041 100644 --- a/hermes_state.py +++ b/hermes_state.py @@ -579,6 +579,36 @@ def _db_opens_cleanly(db_path: Path) -> Optional[str]: return "; ".join(problems[:3]) conn.execute("SELECT COUNT(*) FROM sessions").fetchone() + # FTS5 read probe: run a representative MATCH query against the + # messages_fts* virtual tables. The FTS *write* probe above catches + # the corruption class where base tables read fine but writes fail + # through the triggers (#50502). It does NOT catch partial FTS5 + # index corruption — bad shadow-table segments where reads still + # parse but MATCH / snippet / rank queries error out with + # "database disk image is malformed". session_search, /resume title + # resolution, and any feature relying on FTS5 discovery then break + # silently because the official repair tool's check-only path + # reports the DB as healthy. #66724. + try: + for fts_table in ("messages_fts", "messages_fts_trigram"): + # No-op queries against the actual FTS5 APIs the search + # tools use. The trigram table is included because it backs + # the title-resolution path; either corruption mode would + # break session recall without this probe. Empty MATCH + # string is the safest probe — every FTS5 index accepts it + # without requiring populated content. + conn.execute( + f"SELECT 1 FROM {fts_table} WHERE {fts_table} MATCH '' LIMIT 1" + ).fetchone() + except sqlite3.OperationalError as exc: + msg = str(exc).lower() + if "no such table" in msg or "no such column" in msg: + # FTS5 not built yet (brand new file mid-init) — not the + # corruption class we probe. + pass + else: + return f"fts5 read probe failed on {fts_table}: {exc}" + # FTS write probe: drive a row through the messages_fts* triggers in a # transaction that is always rolled back, so a corrupt FTS index that # rejects writes is caught even though reads look healthy. The probe is