You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Desktop can persist the composer model/provider in localStorage and then send that pair on session.create as a per-session override. The same persistent slot was used for two different meanings:
a real manual model-picker choice, which should persist across new chats/restarts
a mirror of the profile default loaded from /api/model/info, which should keep following config.yaml
When the profile default changed outside the renderer (for example CLI/config now says provider: openai-codex, default: gpt-5.5), a stale default-derived composer value could still be sent as a no-session override. That matches the reporter's dump: config.yaml showed OpenAI Codex, while the desktop turn was built as provider=nous model=openai/gpt-5.5.
Root Cause
refreshCurrentModel() skipped any non-empty composer model, because it could not tell a user picker choice from a default mirror. The second post-fetch race guard had the same unconditional skip.
Fix
Add a tiny persisted source marker for the composer model:
manual for explicit desktop model-picker selections
default for profile-default refreshes and Settings -> Model saves
missing/legacy source is treated as default-derived, so stale v0.18.0 localStorage can self-heal
refreshCurrentModel() now preserves only manual choices. Default-derived and legacy composer state refreshes from /api/model/info, so new Desktop chats stop overriding the correct OpenAI Codex config with a stale Nous route.
Tests
npm --workspace apps/desktop run test:ui -- src/app/session/hooks/use-model-controls.test.tsx
npm --workspace apps/desktop run typecheck
cd apps/desktop && npx eslint src/app/desktop-controller.tsx src/app/session/hooks/use-model-controls.ts src/app/session/hooks/use-model-controls.test.tsx src/store/session.ts
Replaces #58527 (source branch was on kohoj fork, re-opened from yingliang-zhang fork to enable CI). Original author: @kohoj
Related: #65896 merged the source-marker repair on the current Desktop paths. This older branch still edits the deleted desktop-controller.tsx entrypoint, so it is no longer a valid duplicate anchor; #58527 remains historical related work.
Thanks for the triage. To clarify: #58527 was opened from the kohoj fork, which does not have CI access (GitHub Actions can't run on fork PRs without explicit maintainer approval). #62486 is the re-opened version from the yingliang-zhang fork specifically to enable CI runs — the PR body already notes "Replaces #58527". The code is identical; this is not a competing duplicate but a CI-enabled replacement. #58527 can be closed once #62486 is merged.
Rebased onto current main in #65896 (authorship preserved — commit by @kohoj; thanks @yingliang-zhang for re-opening it for CI). After ~715 commits it no longer applied:
desktop-controller.tsx (the file this patched) was deleted on main — refactored into contrib/wiring.tsx. Moved the onMainModelChanged → setCurrentModelSource('default') write there.
use-model-controls.ts conflicted (main renamed activeSessionId→liveSessionId and dropped the hook's activeSessionId prop). Re-applied the manual/default source logic on the current shape and fixed the new test to the current signature.
Behavior is identical to your fix; only the wiring moved. Green on #65896: full vitest --project ui (1314), typecheck, lint, build. Superseding here; will close once #65896 merges. Nice diagnosis — the source-marker split is exactly right.
alt-glitch
added
needs-decision
Awaiting maintainer decision before any implementation
P2
Medium — degraded but workaround exists
and removed
duplicate
This issue or pull request already exists
P3
Low — cosmetic, nice to have
labels
Jul 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
area/configConfig system, migrations, profilescomp/desktopElectron desktop app (apps/desktop/*)needs-decisionAwaiting maintainer decision before any implementationP2Medium — degraded but workaround existssweeper:blast-moderateSweeper blast radius: moderate — a subsystem or single platformsweeper:risk-compatibilitySweeper risk: may break existing users, config, migrations, defaults, or upgradessweeper:risk-session-stateSweeper risk: may lose/corrupt/mis-associate session or context statetype/bugSomething isn't working
5 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Fixes #58498.
Desktop can persist the composer model/provider in localStorage and then send that pair on
session.createas a per-session override. The same persistent slot was used for two different meanings:/api/model/info, which should keep followingconfig.yamlWhen the profile default changed outside the renderer (for example CLI/config now says
provider: openai-codex,default: gpt-5.5), a stale default-derived composer value could still be sent as a no-session override. That matches the reporter's dump:config.yamlshowed OpenAI Codex, while the desktop turn was built asprovider=nous model=openai/gpt-5.5.Root Cause
refreshCurrentModel()skipped any non-empty composer model, because it could not tell a user picker choice from a default mirror. The second post-fetch race guard had the same unconditional skip.Fix
Add a tiny persisted source marker for the composer model:
manualfor explicit desktop model-picker selectionsdefaultfor profile-default refreshes and Settings -> Model savesrefreshCurrentModel()now preserves onlymanualchoices. Default-derived and legacy composer state refreshes from/api/model/info, so new Desktop chats stop overriding the correct OpenAI Codex config with a stale Nous route.Tests
npm --workspace apps/desktop run test:ui -- src/app/session/hooks/use-model-controls.test.tsxnpm --workspace apps/desktop run typecheckcd apps/desktop && npx eslint src/app/desktop-controller.tsx src/app/session/hooks/use-model-controls.ts src/app/session/hooks/use-model-controls.test.tsx src/store/session.ts