From af1ee81f06276f8b776ae76dbf99ebbb1205076a Mon Sep 17 00:00:00 2001
From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Date: Fri, 22 May 2026 08:00:49 +0200
Subject: [PATCH] fix(chat): resolve session model before activating
---
static/sessions.js | 6 ++-
.../test_session_model_resolution_on_load.py | 46 +++++++++++++++++++
2 files changed, 51 insertions(+), 1 deletion(-)
create mode 100644 tests/test_session_model_resolution_on_load.py
diff --git a/static/sessions.js b/static/sessions.js
index d864e0a6..08a1e381 100644
--- a/static/sessions.js
+++ b/static/sessions.js
@@ -567,10 +567,14 @@ async function loadSession(sid){
if (_msgInner && currentSid !== sid) _msgInner.innerHTML = '
Loading conversation...
';
}
// Phase 1: Load metadata only (~1KB) for fast session switching.
+ // Resolve model immediately: old sessions can persist stale provider-shaped
+ // IDs (e.g. openai/gpt-5.4-mini) and assigning those to S.session creates a
+ // short race where the composer can display/send the wrong model before the
+ // deferred resolver catches up.
// Guard against network/server failures to prevent a permanently stuck loading state.
let data;
try {
- data = await api(`/api/session?session_id=${encodeURIComponent(sid)}&messages=0&resolve_model=0`);
+ data = await api(`/api/session?session_id=${encodeURIComponent(sid)}&messages=0&resolve_model=1`);
} catch(e) {
const _msgInner = $('msgInner');
if(_msgInner){
diff --git a/tests/test_session_model_resolution_on_load.py b/tests/test_session_model_resolution_on_load.py
new file mode 100644
index 00000000..fcb29157
--- /dev/null
+++ b/tests/test_session_model_resolution_on_load.py
@@ -0,0 +1,46 @@
+"""Regression tests for stale session model hydration in the WebUI.
+
+Old sessions can persist provider-shaped model IDs such as ``openai/gpt-5.4-mini``
+after the active runtime moved to OpenAI Codex ``gpt-5.5``. The first
+``loadSession()`` metadata request must ask the backend for the resolved model so
+that the composer state cannot briefly use the stale raw value for display or the
+next chat-start payload.
+"""
+
+from pathlib import Path
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+SESSIONS_JS = (REPO_ROOT / "static" / "sessions.js").read_text(encoding="utf-8")
+
+
+def _extract_function(src: str, signature: str) -> str:
+ start = src.find(signature)
+ assert start >= 0, f"missing function signature: {signature}"
+ brace = src.find("{", start)
+ assert brace >= 0, f"missing function body for: {signature}"
+ depth = 0
+ for idx in range(brace, len(src)):
+ ch = src[idx]
+ if ch == "{":
+ depth += 1
+ elif ch == "}":
+ depth -= 1
+ if depth == 0:
+ return src[start : idx + 1]
+ raise AssertionError(f"unterminated function body for: {signature}")
+
+
+def test_load_session_initial_metadata_request_resolves_model_before_state_assignment():
+ body = _extract_function(SESSIONS_JS, "async function loadSession(sid")
+ metadata_fetch = "messages=0&resolve_model=1"
+ stale_metadata_fetch = "messages=0&resolve_model=0"
+ assignment = "S.session=data.session"
+
+ assert metadata_fetch in body, (
+ "loadSession() must resolve model metadata on the initial fetch so stale "
+ "persisted models like openai/gpt-5.4-mini cannot become active composer state"
+ )
+ assert stale_metadata_fetch not in body[: body.index(assignment)], (
+ "loadSession() must not assign S.session from unresolved metadata before the "
+ "backend has normalized stale model/provider combinations"
+ )