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" + )