fix(state-db): skip read indexes an older schema can't hold instead of rolling back the rest

On a pre-2026-07-28 Agent schema, sessions has no last_activity_at, so the
fingerprint index's CREATE failed inside the single BEGIN IMMEDIATE and rolled
back idx_messages_session too, leaving messages unindexed: the exact older-agent
case the troubleshooting doc says the tool fixes. Gate each index on its columns
being present and report 'skipped'. (Opus advisor SHOULD-FIX on #7445.)
This commit is contained in:
nesquena-hermes committed 2026-09-22 22:56:02 +00:00
1 parent 8635487d9b
commit b6d2a2ef17
2 files changed
+32

No files matched your search

+8
View File
@@ -92,6 +92,14 @@ def ensure_read_indexes(db_path, *, confirmed_drained=False, lock_file=None):
db.execute("BEGIN IMMEDIATE")
try:
for name, (table, keys) in INDEXES.items():
# An older Agent schema can lack a column one index keys on
# (sessions.last_activity_at is newer than
# messages.timestamp). Skip that index instead of letting its
# CREATE fail and roll back the ones this schema supports.
present = {r[1] for r in db.execute(f"PRAGMA table_info({table})")}
if any(col not in present for col, _ in keys):
statuses[name] = "skipped"
continue
existing = db.execute("SELECT tbl_name FROM sqlite_master WHERE name=?", (name,)).fetchone()
if existing:
actual = tuple((r[2], r[4]) for r in db.execute(f"PRAGMA index_xinfo({name})") if r[5])
@@ -248,6 +248,30 @@ def _maintenance_schema(path):
)
def test_maintenance_skips_indexes_an_older_schema_cannot_hold(tmp_path, maintenance_without_fcntl):
"""A legacy ``sessions`` table without ``last_activity_at`` must not roll back
the message indexes the schema does support."""
module = maintenance_without_fcntl
path = tmp_path / "state.db"
with closing(sqlite3.connect(str(path))) as conn:
conn.executescript(
"""
CREATE TABLE sessions(source TEXT, id TEXT, message_count INTEGER);
CREATE TABLE messages(session_id TEXT, timestamp REAL, role TEXT);
"""
)
result = module.ensure_read_indexes(path, confirmed_drained=True)
assert result == {
"idx_messages_session": "created",
"idx_messages_session_role": "created",
"idx_sessions_webui_fingerprint": "skipped",
}
with closing(sqlite3.connect(str(path))) as conn:
names = {r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='index'")}
assert {"idx_messages_session", "idx_messages_session_role"} <= names
assert "idx_sessions_webui_fingerprint" not in names
@pytest.fixture
def maintenance_without_fcntl(monkeypatch):
"""Import the maintenance module as a platform without ``fcntl`` sees it."""