diff --git a/CHANGELOG.md b/CHANGELOG.md index 185f15f1..7bcc308f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Rebuild the WebUI session index during startup recovery when `_index.json` is missing, even when no `.bak` session restore occurs, so large state directories avoid repeated full-scan `/api/sessions` fallback after an index loss. + ## [v0.51.95] — 2026-05-20 — Release BS (stage-388 — 5-PR batch — live tool callback event dedup + browser-only dashboard links + messaging transcript merge alignment + Geist Contrast skin + SSE runtime diagnostics) diff --git a/api/session_recovery.py b/api/session_recovery.py index d00c6d68..7dd756da 100644 --- a/api/session_recovery.py +++ b/api/session_recovery.py @@ -619,12 +619,13 @@ def recover_all_sessions_on_startup( "If you weren't expecting this, check the session list for missing " "messages — see #1558.", restored, scanned, ) - if rebuild_index: - try: - from api.models import _write_session_index + if rebuild_index: + try: + from api.models import SESSION_INDEX_FILE, _write_session_index + if restored or not SESSION_INDEX_FILE.exists(): _write_session_index(updates=None) - except Exception as exc: - logger.warning("recover_all_sessions_on_startup: index rebuild failed: %s", exc) + except Exception as exc: + logger.warning("recover_all_sessions_on_startup: index rebuild failed: %s", exc) return { "scanned": scanned, "restored": restored, diff --git a/tests/test_metadata_save_wipe_1558.py b/tests/test_metadata_save_wipe_1558.py index 6ee6e1e7..a4f828e8 100644 --- a/tests/test_metadata_save_wipe_1558.py +++ b/tests/test_metadata_save_wipe_1558.py @@ -270,6 +270,24 @@ def test_recover_all_sessions_on_startup_restores_orphan_bak(temp_session_dir): assert len(restored["messages"]) == 293 +def test_recover_all_sessions_on_startup_rebuilds_missing_index_without_restores(temp_session_dir, monkeypatch): + """Startup recovery must rebuild a missing index even when no .bak restore runs.""" + import api.models as _m + + sid = _make_session_on_disk(temp_session_dir, n_msgs=42) + missing_index = temp_session_dir / "_index.json" + monkeypatch.setattr(_m, "SESSION_INDEX_FILE", missing_index) + assert not missing_index.exists() + + from api.session_recovery import recover_all_sessions_on_startup + result = recover_all_sessions_on_startup(temp_session_dir, rebuild_index=True) + + assert result["restored"] == 0 + index = json.loads(missing_index.read_text(encoding="utf-8")) + assert [entry["session_id"] for entry in index] == [sid] + assert index[0]["message_count"] == 42 + + def test_recover_all_sessions_on_startup_rebuilds_index_after_orphan_restore(temp_session_dir, monkeypatch): """A restored orphan must be visible through the WebUI session index immediately.""" import api.models as _m