fix(state): add REINDEX strategy to repair stale B-tree indexes (#63386)
When PRAGMA integrity_check reports 'wrong # of entries in index' for B-tree indexes (e.g. idx_sessions_handoff_state), the existing repair strategies (FTS rebuild, sqlite_master dedup, drop-FTS+VACUUM) don't address the mismatch. Add Strategy 0.5: run REINDEX to rewrite the index b-tree from canonical table rows before escalating to more destructive strategies.
This commit is contained in:
@@ -748,6 +748,29 @@ def repair_state_db_schema(db_path: Path, *, backup: bool = True) -> Dict[str, A
|
||||
except sqlite3.DatabaseError as exc:
|
||||
logger.warning("state.db FTS in-place rebuild pass failed: %s", exc)
|
||||
|
||||
# ── Strategy 0.5: rebuild stale B-tree indexes (#63386) ──
|
||||
# PRAGMA integrity_check can report "wrong # of entries in index" when a
|
||||
# B-tree index (e.g. idx_sessions_handoff_state) falls out of sync with its
|
||||
# base table. REINDEX rewrites the index b-tree from the canonical table
|
||||
# rows using the existing index definition, fixing the mismatch without
|
||||
# touching data or FTS schema.
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_path), isolation_level=None)
|
||||
try:
|
||||
conn.execute("REINDEX")
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
if _db_opens_cleanly(db_path) is None:
|
||||
report["repaired"] = True
|
||||
report["strategy"] = "reindex_btree"
|
||||
logger.warning(
|
||||
"state.db B-tree indexes rebuilt via REINDEX: %s", db_path
|
||||
)
|
||||
return report
|
||||
except sqlite3.DatabaseError as exc:
|
||||
logger.warning("state.db REINDEX pass failed: %s", exc)
|
||||
|
||||
# ── Strategy 1: de-duplicate sqlite_master (keeps FTS index) ──
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_path), isolation_level=None)
|
||||
|
||||
@@ -487,6 +487,35 @@ def test_repair_noop_db_uses_already_healthy_shortcut(tmp_path):
|
||||
assert report["strategy"] == "already_healthy"
|
||||
|
||||
|
||||
def test_repair_rebuilds_stale_btree_indexes(tmp_path, monkeypatch):
|
||||
"""repair_state_db_schema uses REINDEX for 'wrong # of entries in index'.
|
||||
|
||||
When PRAGMA integrity_check reports a stale B-tree index (e.g.
|
||||
idx_sessions_handoff_state), the FTS-rebuild and dedup strategies don't
|
||||
help — REINDEX rewrites the index b-tree from the canonical table rows.
|
||||
"""
|
||||
db_path = tmp_path / "state.db"
|
||||
_build_healthy_db(db_path)
|
||||
|
||||
_reason = "wrong # of entries in index idx_sessions_handoff_state"
|
||||
_call_count = {"n": 0}
|
||||
_real_check = hermes_state._db_opens_cleanly
|
||||
|
||||
def _simulated_check(path):
|
||||
_call_count["n"] += 1
|
||||
# initial health check + post-Strategy-0 check report corruption;
|
||||
# after REINDEX (Strategy 0.5) the DB is healthy.
|
||||
if _call_count["n"] <= 2:
|
||||
return _reason
|
||||
return _real_check(path)
|
||||
|
||||
monkeypatch.setattr(hermes_state, "_db_opens_cleanly", _simulated_check)
|
||||
|
||||
report = repair_state_db_schema(db_path)
|
||||
assert report["repaired"] is True
|
||||
assert report["strategy"] == "reindex_btree"
|
||||
|
||||
|
||||
def test_select_cached_agent_history_prefers_longer_live_transcript():
|
||||
"""Gateway guard keeps the live transcript when persisted history lags."""
|
||||
from gateway.run import _select_cached_agent_history
|
||||
|
||||
Reference in New Issue
Block a user