From 78c4f1e42598afefdae019dcdf599c6315f4d997 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sun, 19 Apr 2026 23:44:46 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20null/empty=20session=20model=20must=20no?= =?UTF-8?q?t=20trigger=20index=20rebuild=20=E2=80=94=20v0.50.101=20(#753)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Follow-up to #751/#752. Code review identified a case where `_normalize_session_model_in_place` could call `session.save()` (which triggers a full session index rebuild) for sessions with `model: null` or missing model field. Root cause: `_resolve_compatible_session_model(None)` returns `(default_model, True)` when a default exists — which was interpreted as "changed, needs save." But there's nothing to correct for a session with no model; the default is just a fallback for display purposes, not a cross-provider correction worth persisting. Fix: capture `original_model` before calling `_resolve_compatible_session_model`. Only call `session.save()` if `original_model` was non-empty and actually changed. Adds a test asserting `save_calls == []` when `session.model is None`. No behavior change for sessions with a real model (the primary use case of #751 is unaffected). --- CHANGELOG.md | 5 +++++ api/routes.py | 8 ++++++-- tests/test_provider_mismatch.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea97c6..04cbdb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Hermes Web UI -- Changelog +## [v0.50.101] — 2026-04-20 + +### Fixed +- **Session model normalization: null/empty model no longer triggers index rebuild** — sessions with no stored model (`model: null` or missing) now return the provider default without writing to disk. Previously a spurious `session.save()` (and full session index rebuild) could fire for any such session. (#751 follow-up) + ## [v0.50.100] — 2026-04-20 ### Fixed diff --git a/api/routes.py b/api/routes.py index bfbce17..c6785a2 100644 --- a/api/routes.py +++ b/api/routes.py @@ -227,8 +227,12 @@ def _resolve_compatible_session_model(model_id: str | None) -> tuple[str, bool]: def _normalize_session_model_in_place(session) -> str: - effective_model, changed = _resolve_compatible_session_model(getattr(session, "model", None)) - if changed and effective_model and getattr(session, "model", None) != effective_model: + original_model = getattr(session, "model", None) or "" + effective_model, changed = _resolve_compatible_session_model(original_model or None) + # Only persist the correction if the session had an explicit model that needed changing. + # Sessions with no model stored (empty/None) get the effective default returned without + # a disk write — no need to rebuild the index for a fill-in-blank operation. + if changed and effective_model and original_model and original_model != effective_model: session.model = effective_model session.save(touch_updated_at=False) return effective_model diff --git a/tests/test_provider_mismatch.py b/tests/test_provider_mismatch.py index 255a8a2..9377178 100644 --- a/tests/test_provider_mismatch.py +++ b/tests/test_provider_mismatch.py @@ -459,3 +459,36 @@ def test_unknown_prefix_model_passes_through_unchanged(monkeypatch): assert effective == custom_model, ( f"Expected '{custom_model}', got '{effective}'" ) + + +def test_empty_model_session_does_not_trigger_save(monkeypatch): + """Sessions with no model stored must not trigger session.save() — index rebuild is expensive.""" + import api.routes as routes + + monkeypatch.setattr( + routes, + "get_available_models", + lambda: { + "active_provider": "openai-codex", + "default_model": "gpt-5.4-mini", + }, + ) + + save_calls = [] + + class DummySession: + def __init__(self): + self.model = None # no model stored + + def save(self, touch_updated_at=True): + save_calls.append(touch_updated_at) + + session = DummySession() + effective = routes._normalize_session_model_in_place(session) + + # Must return the default, but must NOT write to disk + assert effective == "gpt-5.4-mini" + assert save_calls == [], ( + "_normalize_session_model_in_place must not call session.save() when " + "the session has no stored model — no correction needed, just a fallback." + )