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 1/6] 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"
+ )
From 6bcc9689aae97b78d2cdedabff0011f91897106a Mon Sep 17 00:00:00 2001
From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Date: Fri, 22 May 2026 08:10:50 +0200
Subject: [PATCH 2/6] fix(chat): keep new session model authoritative
---
static/boot.js | 28 +++++++++++++------
static/sessions.js | 13 +++++++--
tests/test_new_chat_default_model_frontend.py | 21 ++++++++++++++
3 files changed, 51 insertions(+), 11 deletions(-)
diff --git a/static/boot.js b/static/boot.js
index 67aa84c4..d041548c 100644
--- a/static/boot.js
+++ b/static/boot.js
@@ -1576,20 +1576,30 @@ function applyBotName(){
// options are enough for first paint; the dynamic provider list can settle
// after the saved session is visible.
const _hydrateBootModelDropdown=()=>populateModelDropdown().then(()=>{
+ const sessionModelState=S.session&&S.session.model
+ ? {model:S.session.model,model_provider:S.session.model_provider||null}
+ : null;
const savedState=(typeof _readPersistedModelState==='function')
? _readPersistedModelState()
: (localStorage.getItem('hermes-webui-model')?{model:localStorage.getItem('hermes-webui-model'),model_provider:null}:null);
- const savedModel=savedState&&savedState.model;
- // Guardrail: prefer profile/server default on boot when available.
- // Persisted browser model state should not override a valid default model.
- const allowBootSavedModelOverride=!window._defaultModel;
- if(savedModel && $('modelSelect') && allowBootSavedModelOverride){
+ // Active sessions are authoritative. On fresh boot without a restored
+ // session, keep the profile/server default ahead of stale browser model
+ // state when a default exists.
+ const stateToApply=sessionModelState||(!window._defaultModel?savedState:null);
+ const savedModel=stateToApply&&stateToApply.model;
+ if(savedModel && $('modelSelect')){
const applied=(typeof _applyModelToDropdown==='function')
- ? _applyModelToDropdown(savedModel,$('modelSelect'),savedState.model_provider||null)
+ ? (sessionModelState
+ ? _applyModelToDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null)
+ : _applyModelToDropdown(savedState.model,$('modelSelect'),savedState.model_provider||null))
: null;
- if(!applied) $('modelSelect').value=savedModel;
- // If the value didn't take (model not in list), clear the bad pref
- if(!applied&&$('modelSelect').value!==savedModel){
+ if(!applied) $('modelSelect').value=stateToApply.model;
+ // If the value didn't take (model not in list), clear the bad pref only
+ // for persisted browser preferences. Active sessions remain authoritative.
+ if(!applied&&sessionModelState&&typeof _ensureModelOptionInDropdown==='function'){
+ _ensureModelOptionInDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null);
+ }
+ else if(!applied&&!sessionModelState&&$('modelSelect').value!==stateToApply.model){
if(typeof _clearPersistedModelState==='function') _clearPersistedModelState();
else {
localStorage.removeItem('hermes-webui-model');
diff --git a/static/sessions.js b/static/sessions.js
index 08a1e381..d828862b 100644
--- a/static/sessions.js
+++ b/static/sessions.js
@@ -490,8 +490,17 @@ async function newSession(flash, options={}){
const sessionProvider=S.session.model_provider||null;
const currentProvider=currentModelState.model_provider||null;
if(S.session.model!==modelSel.value || sessionProvider !== currentProvider){
- _applyModelToDropdown(S.session.model,modelSel,sessionProvider);
- if(typeof syncModelChip==='function') syncModelChip();
+ let sessionModelApplied=_applyModelToDropdown(S.session.model,modelSel,sessionProvider);
+ if(!sessionModelApplied){
+ const opt=document.createElement('option');
+ opt.value=S.session.model;
+ opt.textContent=typeof getModelLabel==='function'?getModelLabel(S.session.model):S.session.model;
+ opt.dataset.custom='1';
+ opt.dataset.provider=sessionProvider||'';
+ modelSel.appendChild(opt);
+ sessionModelApplied=_applyModelToDropdown(S.session.model,modelSel,sessionProvider);
+ }
+ if(sessionModelApplied&&typeof syncModelChip==='function') syncModelChip();
}
}
// Reset per-session visual state: a fresh chat is idle even if another
diff --git a/tests/test_new_chat_default_model_frontend.py b/tests/test_new_chat_default_model_frontend.py
index e12399bc..121df78f 100644
--- a/tests/test_new_chat_default_model_frontend.py
+++ b/tests/test_new_chat_default_model_frontend.py
@@ -35,6 +35,27 @@ def test_new_chat_syncs_model_picker_when_default_provider_changes_but_model_id_
assert "_applyModelToDropdown(S.session.model,modelSel,sessionProvider)" in fn
+def test_new_chat_inserts_session_model_when_static_picker_lacks_default():
+ fn = _new_session_function()
+ assert "sessionModelApplied" in fn
+ assert "document.createElement('option')" in fn
+ assert "opt.value=S.session.model" in fn
+ assert "opt.dataset.provider=sessionProvider||''" in fn
+ assert "modelSel.appendChild(opt)" in fn
+
+
+def test_boot_model_hydration_prefers_active_session_over_persisted_model():
+ boot_js = Path("static/boot.js").read_text(encoding="utf-8")
+ marker = "const sessionModelState=S.session&&S.session.model"
+ assert marker in boot_js
+ session_branch = boot_js[boot_js.index(marker) : boot_js.index("if(S.session) syncTopbar();", boot_js.index(marker))]
+ assert "_applyModelToDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null)" in session_branch
+ assert "savedState" in session_branch
+ assert session_branch.index("sessionModelState") < session_branch.index("savedState"), (
+ "active session model must be considered before localStorage so stale saved model preferences cannot override new chats"
+ )
+
+
def test_new_chat_does_not_send_stale_dropdown_model_when_session_has_default_model():
assert "model:S.session.model||$('modelSelect').value" in MESSAGES_JS
assert "model_provider:S.session.model_provider||null" in MESSAGES_JS
From 765e5aa0912a3d24bc338d209b03dc68e4a0578d Mon Sep 17 00:00:00 2001
From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Date: Fri, 22 May 2026 08:29:34 +0200
Subject: [PATCH 3/6] fix(chat): hydrate restored session model on boot
---
static/boot.js | 6 ++++++
tests/test_new_chat_default_model_frontend.py | 12 ++++++++++++
2 files changed, 18 insertions(+)
diff --git a/static/boot.js b/static/boot.js
index d041548c..2497df69 100644
--- a/static/boot.js
+++ b/static/boot.js
@@ -1654,6 +1654,12 @@ function applyBotName(){
return;
}
await loadSession(saved);
+ // Hard refresh starts from the static HTML model list. Hydrate the live
+ // catalog after the saved session is known, then re-apply that session's
+ // model before S._bootReady lets syncModelChip reveal the composer label.
+ // Otherwise the chip can display the static default (e.g. GPT-5.4 Mini)
+ // even though S.session already points at the Codex/current model.
+ if(S.session) await _startBootModelDropdown();
// If the restored session has no messages it is an ephemeral scratch pad —
// treat the page as a fresh start rather than resuming a blank conversation.
// loadSession() already ran, so loadDir() has populated the workspace file tree.
diff --git a/tests/test_new_chat_default_model_frontend.py b/tests/test_new_chat_default_model_frontend.py
index 121df78f..43e1bd32 100644
--- a/tests/test_new_chat_default_model_frontend.py
+++ b/tests/test_new_chat_default_model_frontend.py
@@ -56,6 +56,18 @@ def test_boot_model_hydration_prefers_active_session_over_persisted_model():
)
+def test_hard_refresh_hydrates_saved_session_model_before_revealing_model_chip():
+ boot_js = Path("static/boot.js").read_text(encoding="utf-8")
+ load_marker = "await loadSession(saved);"
+ assert load_marker in boot_js
+ saved_restore = boot_js[boot_js.index(load_marker) : boot_js.index("await checkInflightOnBoot(saved);return;", boot_js.index(load_marker))]
+ assert "await _startBootModelDropdown();" in saved_restore
+ assert saved_restore.index("await _startBootModelDropdown();") > saved_restore.index(load_marker)
+ assert saved_restore.index("await _startBootModelDropdown();") < saved_restore.index("S._bootReady=true;"), (
+ "hard refresh must hydrate/re-apply the active session model before S._bootReady lets syncModelChip display stale static HTML defaults"
+ )
+
+
def test_new_chat_does_not_send_stale_dropdown_model_when_session_has_default_model():
assert "model:S.session.model||$('modelSelect').value" in MESSAGES_JS
assert "model_provider:S.session.model_provider||null" in MESSAGES_JS
From ebcf0dabb52db0e80509f5604a0096aa4612e599 Mon Sep 17 00:00:00 2001
From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Date: Fri, 22 May 2026 09:23:33 +0200
Subject: [PATCH 4/6] fix(chat): keep restored session model visible on hard
refresh
---
static/ui.js | 66 +++++++++++++------
tests/test_new_chat_default_model_frontend.py | 21 ++++++
2 files changed, 67 insertions(+), 20 deletions(-)
diff --git a/static/ui.js b/static/ui.js
index cab1ec06..46f9b73c 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -869,6 +869,24 @@ function _applyModelToDropdown(modelId, sel, preferredProviderId){
}
return null;
}
+function _ensureModelOptionInDropdown(modelId, sel, preferredProviderId){
+ if(!modelId||!sel) return null;
+ const applied=_applyModelToDropdown(modelId,sel,preferredProviderId);
+ if(applied) return applied;
+ const opt=document.createElement('option');
+ opt.value=modelId;
+ opt.textContent=typeof getModelLabel==='function'?getModelLabel(modelId):modelId;
+ opt.dataset.custom='1';
+ const provider=preferredProviderId||_providerFromModelValue(modelId)||'';
+ if(provider) opt.dataset.provider=provider;
+ sel.appendChild(opt);
+ sel.value=modelId;
+ if(sel.id==='modelSelect'){
+ if(typeof syncModelChip==='function') syncModelChip();
+ _refreshOpenModelDropdown();
+ }
+ return modelId;
+}
function _modelStateFromAppliedDropdown(sel, modelValue){
const state=(typeof _modelStateForSelect==='function')
? _modelStateForSelect(sel,modelValue)
@@ -4813,28 +4831,36 @@ function syncTopbar(){
}
} else {
const applied=_applyModelToDropdown(currentModel,modelSel,S.session.model_provider||null);
- // If the model isn't in the current provider list, reset to the configured
- // default rather than silently retaining the previous chat's selection (#1771).
+ // If the session model is missing from the current provider list, inject
+ // a session-scoped option instead of displaying the previous/static
+ // selection. Only fall back if that repair path is unavailable.
if(!applied){
- const deferModelCorrection=Boolean(S.session._modelResolutionDeferred);
- const missingModelIsRoutable=_providerDefersMissingModelFallback(S.session.model_provider||window._activeProvider||null);
- // Also defer if a live model fetch is still in flight — the model may be
- // in the list once the fetch completes. Persisting now would corrupt the
- // session with the wrong model before live models arrive (#1169).
- const liveStillPending=window._activeProvider&&_liveModelFetchPending.has(window._activeProvider);
- if(liveStillPending||missingModelIsRoutable){
- // Live fetch in flight — don't touch sel.value or S.session.model yet.
- // _addLiveModelsToSelect() will re-apply S.session.model once done (#1169).
- // Named custom providers/OpenRouter can also route vendor-prefixed IDs
- // outside the static catalog, so preserve the user's explicit choice.
+ const sessionOption=(typeof _ensureModelOptionInDropdown==='function')
+ ? _ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null)
+ : null;
+ if(sessionOption){
+ currentModel=sessionOption;
} else {
- const fallback=_applySessionModelFallback(modelSel);
- if(fallback&&!deferModelCorrection){
- S.session.model=fallback.model;
- S.session.model_provider=fallback.model_provider||null;
- currentModel=fallback.model;
- // Persist the correction so the session doesn't re-inject on next load.
- _persistSessionModelCorrection(fallback.model,S.session.model_provider||null);
+ const deferModelCorrection=Boolean(S.session._modelResolutionDeferred);
+ const missingModelIsRoutable=_providerDefersMissingModelFallback(S.session.model_provider||window._activeProvider||null);
+ // Also defer if a live model fetch is still in flight — the model may be
+ // in the list once the fetch completes. Persisting now would corrupt the
+ // session with the wrong model before live models arrive (#1169).
+ const liveStillPending=window._activeProvider&&_liveModelFetchPending.has(window._activeProvider);
+ if(liveStillPending||missingModelIsRoutable){
+ // Live fetch in flight — don't touch sel.value or S.session.model yet.
+ // _addLiveModelsToSelect() will re-apply S.session.model once done (#1169).
+ // Named custom providers/OpenRouter can also route vendor-prefixed IDs
+ // outside the static catalog, so preserve the user's explicit choice.
+ } else {
+ const fallback=_applySessionModelFallback(modelSel);
+ if(fallback&&!deferModelCorrection){
+ S.session.model=fallback.model;
+ S.session.model_provider=fallback.model_provider||null;
+ currentModel=fallback.model;
+ // Persist the correction so the session doesn't re-inject on next load.
+ _persistSessionModelCorrection(fallback.model,S.session.model_provider||null);
+ }
}
}
}
diff --git a/tests/test_new_chat_default_model_frontend.py b/tests/test_new_chat_default_model_frontend.py
index 43e1bd32..267c3105 100644
--- a/tests/test_new_chat_default_model_frontend.py
+++ b/tests/test_new_chat_default_model_frontend.py
@@ -68,6 +68,27 @@ def test_hard_refresh_hydrates_saved_session_model_before_revealing_model_chip()
)
+def test_hard_refresh_injects_missing_active_session_model_option():
+ boot_js = Path("static/boot.js").read_text(encoding="utf-8")
+ marker = "if(!applied&&sessionModelState&&typeof _ensureModelOptionInDropdown==='function')"
+ assert marker in boot_js
+ branch = boot_js[boot_js.index(marker) : boot_js.index("else if(!applied&&!sessionModelState", boot_js.index(marker))]
+ assert "_ensureModelOptionInDropdown(sessionModelState.model,$('modelSelect'),sessionModelState.model_provider||null)" in branch
+
+
+def test_sync_topbar_preserves_missing_session_model_as_dropdown_option():
+ ui_js = Path("static/ui.js").read_text(encoding="utf-8")
+ assert "function _ensureModelOptionInDropdown" in ui_js
+ sync_topbar = _extract_function(ui_js, "function syncTopbar")
+ branch_start = sync_topbar.index("const applied=_applyModelToDropdown(currentModel,modelSel,S.session.model_provider||null);")
+ session_model_branch = sync_topbar[branch_start:]
+ assert "_ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null)" in session_model_branch
+ assert "const fallback=_applySessionModelFallback(modelSel);" in session_model_branch
+ assert session_model_branch.index("_ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null)") < session_model_branch.index("const fallback=_applySessionModelFallback(modelSel);"), (
+ "active session models missing from the current catalog must be injected before fallback can select the static/default model"
+ )
+
+
def test_new_chat_does_not_send_stale_dropdown_model_when_session_has_default_model():
assert "model:S.session.model||$('modelSelect').value" in MESSAGES_JS
assert "model_provider:S.session.model_provider||null" in MESSAGES_JS
From ef9fe1dfdb38c33efa6408517243ac59ffef0222 Mon Sep 17 00:00:00 2001
From: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
Date: Fri, 22 May 2026 08:29:34 +0200
Subject: [PATCH 5/6] fix(chat): keep model fallback guard before session
option repair
---
static/ui.js | 36 ++++++++++++++++++++----------------
1 file changed, 20 insertions(+), 16 deletions(-)
diff --git a/static/ui.js b/static/ui.js
index 46f9b73c..e8bc9b8c 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -4835,23 +4835,27 @@ function syncTopbar(){
// a session-scoped option instead of displaying the previous/static
// selection. Only fall back if that repair path is unavailable.
if(!applied){
- const sessionOption=(typeof _ensureModelOptionInDropdown==='function')
- ? _ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null)
- : null;
- if(sessionOption){
- currentModel=sessionOption;
+ const deferModelCorrection=Boolean(S.session._modelResolutionDeferred);
+ const missingModelIsRoutable=_providerDefersMissingModelFallback(S.session.model_provider||window._activeProvider||null);
+ // Also defer if a live model fetch is still in flight — the model may be
+ // in the list once the fetch completes. Persisting now would corrupt the
+ // session with the wrong model before live models arrive (#1169).
+ const liveStillPending=window._activeProvider&&_liveModelFetchPending.has(window._activeProvider);
+ if(liveStillPending||missingModelIsRoutable){
+ // Live fetch in flight — don't touch sel.value or S.session.model yet.
+ // _addLiveModelsToSelect() will re-apply S.session.model once done (#1169).
+ // Named custom providers/OpenRouter can also route vendor-prefixed IDs
+ // outside the static catalog, so preserve the user's explicit choice.
+ if(typeof _ensureModelOptionInDropdown==='function'){
+ const sessionOption=_ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null);
+ if(sessionOption) currentModel=sessionOption;
+ }
} else {
- const deferModelCorrection=Boolean(S.session._modelResolutionDeferred);
- const missingModelIsRoutable=_providerDefersMissingModelFallback(S.session.model_provider||window._activeProvider||null);
- // Also defer if a live model fetch is still in flight — the model may be
- // in the list once the fetch completes. Persisting now would corrupt the
- // session with the wrong model before live models arrive (#1169).
- const liveStillPending=window._activeProvider&&_liveModelFetchPending.has(window._activeProvider);
- if(liveStillPending||missingModelIsRoutable){
- // Live fetch in flight — don't touch sel.value or S.session.model yet.
- // _addLiveModelsToSelect() will re-apply S.session.model once done (#1169).
- // Named custom providers/OpenRouter can also route vendor-prefixed IDs
- // outside the static catalog, so preserve the user's explicit choice.
+ const sessionOption=(typeof _ensureModelOptionInDropdown==='function')
+ ? _ensureModelOptionInDropdown(currentModel,modelSel,S.session.model_provider||null)
+ : null;
+ if(sessionOption){
+ currentModel=sessionOption;
} else {
const fallback=_applySessionModelFallback(modelSel);
if(fallback&&!deferModelCorrection){
From d56cd6a31256e10fcacfc5f7e876ef86b89082c3 Mon Sep 17 00:00:00 2001
From: nesquena-hermes <[email protected]>
Date: Fri, 22 May 2026 16:54:23 +0000
Subject: [PATCH 6/6] Stamp CHANGELOG for v0.51.112 (Release CJ / stage-405 /
1-PR)
---
CHANGELOG.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3f47ac7..0255bc7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,12 @@
## [Unreleased]
+## [v0.51.112] — 2026-05-22 — Release CJ (stage-405 — 1-PR — session model authoritative across restore)
+
+### Fixed
+
+- **PR #2737** by @ai-ag2026 — Keep the session model authoritative when a restored session is reactivated. Previously, stale browser-cached picker state could override an active conversation's model in four scenarios: (1) on initial boot when `localStorage` had a different model preference than the active session, (2) on hard refresh when `S._bootReady` revealed the composer chip before the live catalog hydrated, (3) when the session's model wasn't in the current provider catalog (the static/default fallback silently rewrote `S.session.model`), (4) when starting a new session whose model wasn't in the static HTML dropdown. The fix: `loadSession()` now requests `resolve_model=1` so backend normalization happens synchronously with metadata; boot model hydration prefers the active session over `localStorage`; hard refresh re-runs the model dropdown hydration before `_bootReady`; a new `_ensureModelOptionInDropdown()` helper injects a `data-custom='1'` option for models not in the catalog instead of silently rewriting `S.session.model` to the default. 100 LOC of new pytest regression coverage pinning each behavior.
+
## [v0.51.111] — 2026-05-22 — Release CI (stage-404 — 1-PR — keep state.db replays out of sidecar tail)
### Fixed