Skip to content

Release v0.51.295 — stage-3739/3742 (model-pick revert fix #3739 + session-status revert #3742) - #3743

Merged
nesquena-hermes merged 6 commits into
masterfrom
release/stage-3739-3742
Jun 6, 2026
Merged

Release v0.51.295 — stage-3739/3742 (model-pick revert fix #3739 + session-status revert #3742)#3743
nesquena-hermes merged 6 commits into
masterfrom
release/stage-3739-3742

Conversation

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Release stage for v0.51.295 — two small independent fixes batched (no file overlap).

#3739fixes #3737 — model picker silently reverts explicit cross-family pick

v0.51.290 regression (#3448 profile-aware resolution). @someaka. Honors an explicit pick across BOTH resolver branches + a toast on genuine mismatch.

Deep-review notes (this stage):

#3742 → reverts #3570 — manual per-session status labels

Maintainer-authored, pure deletion (+4/−118). localStorage-only (no cross-device persistence, read as a bug) + crowded the root context menu.

Gate (all green)

  • Full suite: 8090 passed, 0 failed
  • Codex regression (diff vs master): SAFE TO SHIP
  • Opus correctness: SAFE TO SHIP
  • ESLint runtime / scope-undef / browser-smoke: CLEAN
  • revert-guard: origin/master is an ancestor (no stale-base revert risk)

Tests added: test_issue3737_explicit_pick_client_wiring.py (4), author's resolver tests in test_provider_mismatch.py (2), refresh-persistence test realigned.

Known non-blocking follow-ups: mismatch toast string not yet i18n-wrapped; explicit-pick is single-use per send (net improvement, server-persist is a future enhancement). Closes #3737. Reverts #3570.

John Doe and others added 6 commits June 6, 2026 20:19
…y selection (#3737)

When a user changes the model in the composer dropdown and sends,
_resolve_compatible_session_model_state previously had no way to
distinguish an explicit user pick from stale session state. The
profile-aware branch (v0.51.290, PR #3448) and the legacy block
both rewrote bare cross-family models to the profile default, and
the client unconditionally applied effective_model — silently
discarding the user's choice.

Backend: accept explicit_model_pick flag (default False) on
_resolve_compatible_session_model_state. Guard both the
profile-aware branch (routes.py:2024) and the legacy block
(routes.py:2124) to skip cross-provider normalization when set.
_handle_chat_start extracts the flag and passes it through.

Frontend: consult _readPendingSessionModel (sessionStorage, 10-min
window) to detect explicit picks and include the flag. Add a toast
as defense-in-depth when the server still returns effective_model.

Closes #3737
Greptile P2-1: compare model_provider in pending pick detection,
not just model name, to avoid false-positive flag when the
session provider changes between pick and send.

Greptile P2-2: only show the defense-in-depth toast when an
explicit pick was actually overridden — stale-session
normalizations are expected behavior and should be silent.

Add two regression tests for the profile-branch guard:
- explicit_model_pick=True → cross-family model survives
- explicit_model_pick=False → existing normalization preserved
The manual per-session status labels (Todo / In Progress / Done) added in
v0.51.284 (#3570) stored state only in browser localStorage keyed by session
id, with no server-side backing — so labels did not persist across browsers
or devices (a user who labeled sessions on one machine saw none after moving
to a laptop). They also rendered as three flat top-level entries in the
session context menu, crowding the root menu.

Per maintainer decision, remove the feature entirely for now. It can be
reintroduced later with proper server-side persistence and a less intrusive
menu treatment.

Removes:
- JS state/cycle helpers + SESSION_MANUAL_STATUS_KEY (static/sessions.js)
- context-menu status entries + sidebar status badge render
- .session-manual-status* CSS (static/style.css)
- session_status_* locale strings across all locales (static/i18n.js)

Full suite: 8084 passed, 0 failed. ESLint runtime gate: clean.

reverts #3570
…atch)

Codex found the explicit_model_pick flag never engaged in the normal flow: boot.js
modelSelect.onchange cleared the pending-pick marker right after /api/session/update,
so by the time send() ran _readPendingSessionModel returned null, _explicitPick was
false, and the server's profile-provider branch still reverted the cross-family pick
(the exact #3737 bug). The flag only worked in the rare race where send beat the
session-update round-trip.

Fix (Codex prescription): do NOT clear the marker in onchange; clear it in send()
immediately after reading a matching pending pick, so it's consumed for that send only.
onchange still RECORDS the pick (_rememberPendingSessionModel) — only the premature
clear is removed.
…consumes)

Static source guards for the Codex clear-timing fix: onchange must record the
pending pick and NOT clear it post-session-update; send() must consume (clear) it
only after reading a matching _explicitPick, and send the flag only when truthy.
Complements the author's resolver-level tests in test_provider_mismatch.py.
…ck clear

The Codex clear-timing fix moved the pending-pick clear out of modelSelect.onchange
into send() (consume-on-send). test_model_selection_records_pending_state_before_async_session_update
asserted the OLD onchange-clears behavior (assert _clearPendingSessionModel in body).
Updated to assert the NEW correct behavior (onchange must NOT clear it — it survives to
send). The test's core refresh-survives invariant (marker recorded before the async
session-update; reapplied on load) is unchanged and still passes; only the stale
clear-location assertion is flipped. Not a regression-blessing: the refresh-survives
feature is intact, the marker lifecycle is more correct.
@nesquena-hermes
nesquena-hermes merged commit 65c4bc9 into master Jun 6, 2026
11 checks passed
@nesquena-hermes
nesquena-hermes deleted the release/stage-3739-3742 branch June 6, 2026 20:39
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This release batches two independent fixes: #3739 corrects the v0.51.290 regression where an explicitly chosen cross-family model was silently reverted on send() (root cause: onchange cleared the pending-pick marker before send() could read it; server lacked the explicit_model_pick flag in both resolver branches), and #3742 reverts the localStorage-only per-session status labels from #3570.

  • fix: honor explicit model pick, suppress silent revert on cross-family selection (#3737) #3739 — explicit-pick wiring: modelSelect.onchange no longer clears _pendingSessionModel; send() now reads it, gates explicit_model_pick on a model+provider match, clears it once, and passes the flag to /api/chat/start; _resolve_compatible_session_model_state honors the flag in both the profile-aware branch (bare cross-family names) and the legacy bare-prefix branch. A mismatch toast is shown when the server still normalizes despite the flag. The slash-qualified cross-provider path in the profile branch (line 2044) does not yet have the same guard, leaving that narrower case unaddressed.
  • revert(sidebar): remove manual session status labels (#3570) #3742 — session-status revert: Removes JS state, cycle logic, context-menu entries, sidebar badge render, CSS, and all 12 locale string sets for the manual status feature; the deletion is clean and symmetric.

Confidence Score: 4/5

Safe to merge for the reported bug; one narrow cross-provider edge case remains open.

The core timing fix (marker surviving onchange → send) and the two server-side resolver guards are correct and well-tested. The one gap is the profile-aware branch at line 2044: slash-qualified cross-family models (e.g., openai/gpt-5.4-mini under an anthropic profile) still bypass the explicit_model_pick guard and get reverted to the profile default, so the silent-revert symptom from #3737 can still occur for that model format.

api/routes.py line 2044 — the slash-qualified cross-family profile branch has no explicit_model_pick guard; all other changed files are clean.

Important Files Changed

Filename Overview
api/routes.py Adds explicit_model_pick parameter to _resolve_compatible_session_model_state and wires it through _handle_chat_start; both the profile-aware branch (cross-family bare name) and the legacy bare-prefix branch now honor the flag — but slash-qualified cross-family models in the profile branch remain unguarded.
static/boot.js Removes the premature _clearPendingSessionModel call from modelSelect.onchange; the marker now survives until send() consumes it, fixing the timing bug that was the root cause of #3737.
static/messages.js send() now reads and consumes the pending explicit-pick marker, sends explicit_model_pick to the server, and shows a mismatch toast on revert; marker is cleared before the API call (single-use on failure) and the toast string is not yet i18n-wrapped.
static/sessions.js Clean deletion of the manual-status localStorage state, cycle logic, context-menu entries, and sidebar badge render from #3570 — no logic concerns.
static/i18n.js Removes the four session_status_* locale keys from all 12 locales; deletion is symmetric and complete across all locale blocks.
tests/test_issue3737_explicit_pick_client_wiring.py New static source-structure tests guard the onchange-not-clear and send-consumes-marker invariants; assertions are precise and lock the exact fix from regressing silently.
tests/test_provider_mismatch.py Two new resolver tests verify that explicit_model_pick=True preserves a cross-family bare model and explicit_model_pick=False still normalizes; clean and targeted.
tests/test_model_selection_refresh_persistence.py Updated the existing assertion to now assert _clearPendingSessionModel is absent from onchange; correctly reflects the intent of the fix.
static/style.css Removes the five session-manual-status CSS rules; clean deletion, no concerns.
CHANGELOG.md Adds a Removed entry for the session-status revert; description is accurate and thorough.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant DS as modelSelect.onchange (boot.js)
    participant SS as /api/session/update
    participant SN as send() (messages.js)
    participant CS as /api/chat/start
    participant R as _resolve_compatible_session_model_state

    U->>DS: picks cross-family model (e.g. gpt-5.5)
    DS->>DS: _rememberPendingSessionModel(sid, model, provider)
    DS->>SS: POST /api/session/update
    Note over DS: marker NOT cleared here (fix for #3737)
    U->>SN: submits message
    SN->>SN: _readPendingSessionModel(sid)
    SN->>SN: "_explicitPick = (pending matches current model)"
    SN->>SN: _clearPendingSessionModel(sid) [consumed]
    SN->>CS: "POST {model, explicit_model_pick: true}"
    CS->>R: "_resolve_compatible_session_model_state(..., explicit_model_pick=True)"
    alt profile-aware branch — cross-family bare name
        R-->>CS: (model, profile_provider, False) honored
    else legacy bare-prefix branch
        R-->>CS: (model, requested_provider, False) honored
    end
    CS-->>SN: "{effective_model, ...}"
    alt "effective_model != sentModel"
        SN->>U: showToast(model changed — profile provider mismatch)
    end
Loading

Comments Outside Diff (1)

  1. api/routes.py, line 2044-2046 (link)

    P2 Slash-qualified cross-family model not guarded by explicit_model_pick

    The explicit_model_pick guard at line 2023 only fires when the model name starts with a bare prefix (gpt, claude, gemini) and model_family is detected. For a slash-qualified model like openai/gpt-5.4-mini picked explicitly under an anthropic profile, model_family resolves to "" (the bare-prefix loop doesn't fire since the string starts with "openai/"), so execution falls through to this block and the model is still reverted to the profile default — the same silent revert bug bug(models): picked model reverts to previous/default model on send (likely #3448 profile-aware resolution, v0.51.290) #3737 fixed for bare names. Slash-qualified cross-provider sessions would remain broken after this PR.

Reviews (1): Last reviewed commit: "test(#3737): realign refresh-persistence..." | Re-trigger Greptile

Comment thread static/messages.js
if(startData.effective_model && S.session){
const _sentModel=_modelState.model;
if(_explicitPick && _sentModel && startData.effective_model!==_sentModel && typeof showToast==='function'){
showToast('Model '+_sentModel+' changed to '+startData.effective_model+' — profile provider mismatch', 5000);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The mismatch toast message is a raw English string with string concatenation and is not wrapped with the t() i18n helper. The PR notes this as a known follow-up, but worth tracking as a concrete anchor. The t('model_mismatch_toast') key would also need to be added to i18n.js for all locales.

Suggested change
showToast('Model '+_sentModel+' changed to '+startData.effective_model+' — profile provider mismatch', 5000);
showToast((t('model_mismatch_toast')||'Model {0} changed to {1} — profile provider mismatch').replace('{0}',_sentModel).replace('{1}',startData.effective_model), 5000);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread static/messages.js
// session-update) so it survives the normal pick→update→send flow; clear it here
// once read so a later send of an unchanged dropdown isn't treated as an explicit
// pick. (#3739/#3737, Codex catch)
if(_explicitPick && typeof _clearPendingSessionModel==='function') _clearPendingSessionModel(activeSid);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Marker consumed before API call — explicit pick is single-use on failure

_clearPendingSessionModel is called before await api('/api/chat/start', …), so if the network request fails or the server returns an error, the pending-pick marker is already gone. A retry send() would compute _explicitPick = false and the server would silently revert the cross-family model back to the profile default. The PR notes this as an intentional "single-use per send" trade-off with a future server-persist path, but it could confuse users who retry after a transient failure.

SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
 + session-status revert nesquena#3742) (nesquena#3743)

* fix: honor explicit model pick, suppress silent revert on cross-family selection (nesquena#3737)

When a user changes the model in the composer dropdown and sends,
_resolve_compatible_session_model_state previously had no way to
distinguish an explicit user pick from stale session state. The
profile-aware branch (v0.51.290, PR nesquena#3448) and the legacy block
both rewrote bare cross-family models to the profile default, and
the client unconditionally applied effective_model — silently
discarding the user's choice.

Backend: accept explicit_model_pick flag (default False) on
_resolve_compatible_session_model_state. Guard both the
profile-aware branch (routes.py:2024) and the legacy block
(routes.py:2124) to skip cross-provider normalization when set.
_handle_chat_start extracts the flag and passes it through.

Frontend: consult _readPendingSessionModel (sessionStorage, 10-min
window) to detect explicit picks and include the flag. Add a toast
as defense-in-depth when the server still returns effective_model.

Closes nesquena#3737

* fix: tighten explicit-pick detection and add regression tests (nesquena#3737)

Greptile P2-1: compare model_provider in pending pick detection,
not just model name, to avoid false-positive flag when the
session provider changes between pick and send.

Greptile P2-2: only show the defense-in-depth toast when an
explicit pick was actually overridden — stale-session
normalizations are expected behavior and should be silent.

Add two regression tests for the profile-branch guard:
- explicit_model_pick=True → cross-family model survives
- explicit_model_pick=False → existing normalization preserved

* revert(sidebar): remove manual session status labels (nesquena#3570)

The manual per-session status labels (Todo / In Progress / Done) added in
v0.51.284 (nesquena#3570) stored state only in browser localStorage keyed by session
id, with no server-side backing — so labels did not persist across browsers
or devices (a user who labeled sessions on one machine saw none after moving
to a laptop). They also rendered as three flat top-level entries in the
session context menu, crowding the root menu.

Per maintainer decision, remove the feature entirely for now. It can be
reintroduced later with proper server-side persistence and a less intrusive
menu treatment.

Removes:
- JS state/cycle helpers + SESSION_MANUAL_STATUS_KEY (static/sessions.js)
- context-menu status entries + sidebar status badge render
- .session-manual-status* CSS (static/style.css)
- session_status_* locale strings across all locales (static/i18n.js)

Full suite: 8084 passed, 0 failed. ESLint runtime gate: clean.

reverts nesquena#3570

* fix(nesquena#3737): keep explicit-pick marker until send consumes it (Codex catch)

Codex found the explicit_model_pick flag never engaged in the normal flow: boot.js
modelSelect.onchange cleared the pending-pick marker right after /api/session/update,
so by the time send() ran _readPendingSessionModel returned null, _explicitPick was
false, and the server's profile-provider branch still reverted the cross-family pick
(the exact nesquena#3737 bug). The flag only worked in the rare race where send beat the
session-update round-trip.

Fix (Codex prescription): do NOT clear the marker in onchange; clear it in send()
immediately after reading a matching pending pick, so it's consumed for that send only.
onchange still RECORDS the pick (_rememberPendingSessionModel) — only the premature
clear is removed.

* test(nesquena#3737): lock client clear-timing wiring (onchange records, send consumes)

Static source guards for the Codex clear-timing fix: onchange must record the
pending pick and NOT clear it post-session-update; send() must consume (clear) it
only after reading a matching _explicitPick, and send the flag only when truthy.
Complements the author's resolver-level tests in test_provider_mismatch.py.

* test(nesquena#3737): realign refresh-persistence test to the moved pending-pick clear

The Codex clear-timing fix moved the pending-pick clear out of modelSelect.onchange
into send() (consume-on-send). test_model_selection_records_pending_state_before_async_session_update
asserted the OLD onchange-clears behavior (assert _clearPendingSessionModel in body).
Updated to assert the NEW correct behavior (onchange must NOT clear it — it survives to
send). The test's core refresh-survives invariant (marker recorded before the async
session-update; reapplied on load) is unchanged and still passes; only the stale
clear-location assertion is flipped. Not a regression-blessing: the refresh-survives
feature is intact, the marker lifecycle is more correct.

---------

Co-authored-by: John Doe <johndoe@example.com>
Co-authored-by: nesquena-hermes <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(models): picked model reverts to previous/default model on send (likely #3448 profile-aware resolution, v0.51.290)

1 participant