From e1945ff697ab300a09a8ac8ad081397e17994116 Mon Sep 17 00:00:00 2001 From: teknium1 <127238744+teknium1@users.noreply.github.com> Date: Sat, 30 May 2026 01:53:25 -0700 Subject: [PATCH] test(state): cover update_session_model overwrite + getattr-guard text path Follow-up to LengR's #35181 salvage: - gateway text-path uses getattr(self, '_session_db', None) to match the picker callback path (defensive for object.__new__() gateway test pattern). - add SessionDB.update_session_model test asserting it overwrites the COALESCE-pinned model and survives subsequent token updates (#34850). --- gateway/run.py | 5 +++-- scripts/release.py | 1 + tests/test_hermes_state.py | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index 514110a83..09f6f990b 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -10704,10 +10704,11 @@ class GatewayRunner: # Persist the new model to the session DB so the dashboard # shows the updated model (#34850). - if self._session_db is not None: + _sess_db = getattr(self, "_session_db", None) + if _sess_db is not None: try: _sess_entry = self.session_store.get_or_create_session(source) - self._session_db.update_session_model( + _sess_db.update_session_model( _sess_entry.session_id, result.new_model ) except Exception as exc: diff --git a/scripts/release.py b/scripts/release.py index 913d5dc23..11a446a50 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -46,6 +46,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json" # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { "drpelagik@gmail.com": "SeaXen", + "lengr@users.noreply.github.com": "LengR", "metalclaudbot@gmail.com": "HashClawAI", "tonybear55665566@gmail.com": "TonyPepeBear", "kaspersniels@gmail.com": "nielskaspers", diff --git a/tests/test_hermes_state.py b/tests/test_hermes_state.py index a6c33a5cb..8fec76aa6 100644 --- a/tests/test_hermes_state.py +++ b/tests/test_hermes_state.py @@ -152,6 +152,30 @@ class TestSessionLifecycle: session = db.get_session("s1") assert session["model"] == "anthropic/claude-opus-4.6" + def test_update_session_model_overwrites_existing(self, db): + """A mid-session /model switch must overwrite the stored model. + + update_token_counts uses COALESCE(model, ?) (first-writer-wins), so + the dashboard kept showing the original model after a switch (#34850). + update_session_model sets the column unconditionally. + """ + db.create_session(session_id="s1", source="telegram", + model="xiaomi/mimo-v2.5-pro") + # Token updates never change the model once set. + db.update_token_counts("s1", input_tokens=10, output_tokens=5, + model="xiaomi/mimo-v2.5-pro") + assert db.get_session("s1")["model"] == "xiaomi/mimo-v2.5-pro" + + # Explicit switch overwrites it. + db.update_session_model("s1", "xiaomi/mimo-v2.5") + assert db.get_session("s1")["model"] == "xiaomi/mimo-v2.5" + + # And a subsequent token update does NOT revert it (COALESCE no-ops + # because the column is now non-NULL). + db.update_token_counts("s1", input_tokens=10, output_tokens=5, + model="xiaomi/mimo-v2.5-pro") + assert db.get_session("s1")["model"] == "xiaomi/mimo-v2.5" + def test_parent_session(self, db): db.create_session(session_id="parent", source="cli") db.create_session(session_id="child", source="cli", parent_session_id="parent")