From 288cbb55bc67c6a9996ec602264fba049a5b6df7 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Fri, 26 Jun 2026 20:31:42 -0400 Subject: [PATCH] fix(#5021): keep boot model budget across restore paths --- static/boot.js | 4 ++- static/sessions.js | 18 ++++++++----- tests/test_5021_boot_model_redirect_budget.py | 26 ++++++++++++++----- ...t_issue4756_session_visit_model_refresh.py | 21 ++++++++------- 4 files changed, 46 insertions(+), 23 deletions(-) diff --git a/static/boot.js b/static/boot.js index ca640e32..c7316d66 100644 --- a/static/boot.js +++ b/static/boot.js @@ -2278,7 +2278,9 @@ window._applyTitlebarProfileVisibility=_applyTitlebarProfileVisibility; let consumed=false; const readAttempted=(storage=sessionStorage)=>{ try{ - return storage&&storage.getItem?storage.getItem(markerKey)==='1':false; + const attempted=storage&&storage.getItem?storage.getItem(markerKey)==='1':false; + if(attempted) consumed=true; + return attempted; }catch(_){ return false; } diff --git a/static/sessions.js b/static/sessions.js index ae458559..b4754f1d 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1294,14 +1294,18 @@ async function loadSession(sid){ S._pendingSessionToolsets=null; if(typeof populateModelDropdown==='function'){ const modelRefreshSid=sid; - const modelRefreshPromise=Promise.resolve().then(()=>{ - if(_loadingSessionId!==modelRefreshSid) return; - if(!S._bootReady&&typeof window!=='undefined'&&typeof window._startBootModelDropdown==='function'){ + if(!S._bootReady&&typeof window!=='undefined'&&typeof window._startBootModelDropdown==='function'){ + Promise.resolve().then(()=>{ + if(_loadingSessionId!==modelRefreshSid) return; return window._startBootModelDropdown(); - } - return populateModelDropdown({freshness:'session_visit'}); - }).catch(()=>{}); - if(typeof window!=='undefined') window._modelDropdownReady=modelRefreshPromise; + }).catch(()=>{}); + }else{ + const modelRefreshPromise=Promise.resolve().then(()=>{ + if(_loadingSessionId!==modelRefreshSid) return; + return populateModelDropdown({freshness:'session_visit'}); + }).catch(()=>{}); + if(typeof window!=='undefined') window._modelDropdownReady=modelRefreshPromise; + } } if(typeof _hydrateTodosFromSession==='function') _hydrateTodosFromSession(S.session); S.session._modelResolutionDeferred=true; diff --git a/tests/test_5021_boot_model_redirect_budget.py b/tests/test_5021_boot_model_redirect_budget.py index d6b1ae82..8cb04bb0 100644 --- a/tests/test_5021_boot_model_redirect_budget.py +++ b/tests/test_5021_boot_model_redirect_budget.py @@ -12,6 +12,7 @@ ROOT = Path(__file__).resolve().parent.parent BOOT_JS = ROOT / "static" / "boot.js" UI_JS = ROOT / "static" / "ui.js" NODE = shutil.which("node") +BOOT_MARKER_KEY = "hermes-webui-active-profile-bootstrap-401" pytestmark = pytest.mark.skipif( @@ -436,18 +437,31 @@ def test_boot_model_401_does_not_reopen_redirect_after_active_profile_fallback(d def test_boot_model_401_consumes_budget_when_profile_load_succeeds(driver_path): - payload = _run( + first = _run( driver_path, { "kind": "boot-model-only", }, ) - assert payload["redirects"] == ["login?next=%2Fsession%2Fabc%3Fworkspace%3Dtest"] - assert payload["results"][0]["state"]["status"] == "resolved" - assert payload["jsonCalls"] == 0 - assert payload["activeProvider"] is None - assert payload["defaultModel"] is None + second = _run( + driver_path, + { + "kind": "boot-model-only", + "initialStorage": first["storage"], + }, + ) + + assert first["redirects"] == ["login?next=%2Fsession%2Fabc%3Fworkspace%3Dtest"] + assert first["results"][0]["state"]["status"] == "resolved" + assert first["storage"] == {BOOT_MARKER_KEY: "1"} + assert first["jsonCalls"] == 0 + assert first["activeProvider"] is None + assert first["defaultModel"] is None + assert second["redirects"] == [] + assert second["results"][0]["state"]["status"] == "resolved" + assert second["storage"] == {} + assert second["jsonCalls"] == 0 def test_boot_model_success_still_stores_active_provider(driver_path): diff --git a/tests/test_issue4756_session_visit_model_refresh.py b/tests/test_issue4756_session_visit_model_refresh.py index 31722115..94624302 100644 --- a/tests/test_issue4756_session_visit_model_refresh.py +++ b/tests/test_issue4756_session_visit_model_refresh.py @@ -328,22 +328,25 @@ def test_load_session_schedules_async_session_visit_model_refresh_after_metadata body = _extract_function_body(_read_static("sessions.js"), "async function loadSession(") assign_idx = body.index("S.session=data.session") - promise_idx = body.index("const modelRefreshPromise=Promise.resolve().then(") boot_guard_idx = body.index("if(!S._bootReady&&typeof window!=='undefined'&&typeof window._startBootModelDropdown==='function'){") + boot_promise_idx = body.index("Promise.resolve().then(()=>{", boot_guard_idx) boot_refresh_idx = body.index("return window._startBootModelDropdown();") + else_idx = body.index("}else{", boot_refresh_idx) + promise_idx = body.index("const modelRefreshPromise=Promise.resolve().then(", else_idx) ready_idx = body.index("window._modelDropdownReady=modelRefreshPromise") refresh_idx = body.index("populateModelDropdown({freshness:'session_visit'})") stale_guard_idx = body.index("_loadingSessionId!==modelRefreshSid") - assert assign_idx < promise_idx < boot_guard_idx < boot_refresh_idx < refresh_idx < ready_idx - assert promise_idx < stale_guard_idx < boot_guard_idx + assert assign_idx < boot_guard_idx < boot_promise_idx < boot_refresh_idx < else_idx < promise_idx < refresh_idx < ready_idx + assert boot_promise_idx < stale_guard_idx < boot_refresh_idx + assert promise_idx < body.index("_loadingSessionId!==modelRefreshSid", promise_idx) < refresh_idx -def test_boot_model_dropdown_reuses_inflight_session_visit_refresh(): - body = _extract_function_body(_read_static("boot.js"), "const _startBootModelDropdown=()=>") +def test_boot_model_dropdown_clears_cached_ready_on_401(): + body = _extract_function_body(_read_static("boot.js"), "const _redirectBootModelDropdownIfUnauth=(res)=>") - ready_idx = body.index("const ready=window._modelDropdownReady;") - reuse_idx = body.index("if(ready&&typeof ready.then==='function') return ready;") - hydrate_idx = body.index("const next=_hydrateModelDropdown({redirectIfUnauth:_redirectBootModelDropdownIfUnauth});") + status_idx = body.index("if(!res||res.status!==401) return false;") + clear_idx = body.index("window._modelDropdownReady=null;") + consumed_idx = body.index("if(_bootActiveProfileUnauthRedirectBudget.isConsumed()) return true;") - assert ready_idx < reuse_idx < hydrate_idx + assert status_idx < clear_idx < consumed_idx