fix: model catalog follows the session's backend (web)#160
Merged
Conversation
Switching a session's backend (`/provider` or the picker) left the model catalog showing the OLD backend's models — because the web client fetched `models.list` with NO `provider` field (so the daemon always answered for its default backend, claude) and cached the result globally, never refetching. Codex made it worse: even a correct request returns an empty list until that backend has run once (every provider builds its backend lazily on the first turn), so a never-used codex would have shown a stale claude list indefinitely. - `fetchModels(provider?, force?)` now sends the backend on the wire and caches per-backend; the visible catalog tracks the requested backend and a slow response for a backend the user already switched away from is dropped (switch-race guard). - switching to an unfetched backend clears the previous list synchronously so the picker never flashes the wrong models. - ModelPicker refetches (forced) whenever the focused session's providerId changes, and the bare `/model` slash fetches the focused session's backend rather than the daemon default. A never-used backend still shows an empty catalog until its first turn (models are genuinely unknown until then) — honest, and the picker hides rather than lying. Follow-up worth considering: push a models update to attached clients when a backend first reports its catalog, so the picker auto-populates after the first turn without a re-open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🤖 Gemini code reviewThis PR successfully resolves the issue where the model catalog failed to update when switching a session's backend. It implements per-backend model catalog caching, clears stale models synchronously upon switching to avoid flashing incorrect options, and adds robust unit tests. Findings: 🔴 0 · 🟠 0 · 🟡 0 · 🟢 1 Tokens spent · ⬆️ Input: 4,851 · ⬇️ Output: 256 · Σ Total: 11,338 |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #160 +/- ##
=======================================
Coverage 84.46% 84.46%
=======================================
Files 104 104
Lines 18263 18263
=======================================
Hits 15426 15426
Misses 2837 2837
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Review feedback (#160): the provider-change effect forced a refetch even for a backend already fetched live, so tabbing between sessions on different backends hit the daemon needlessly. Drop force — a live-cached backend now serves from cache instantly, while a not-yet-live backend still refetches. Kept OUTSIDE the picker-open gate on purpose: the /model slash and help modal read the same catalog, so it must track the focused backend regardless of picker state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
The bug you hit: switching a session's backend didn't refresh the model catalogue — the picker kept showing the previous backend's models.
Root cause (two compounding issues)
fetchModelssent{type:"models.list"}with noproviderfield, so the daemon always answered for its default backend (claude), and the result was cached globally and never refetched. So every session — codex, pi, whatever — showed claude's models.#query, codex's#proc, pi's rpc all spawn on the first turn), so a never-used backend has an empty catalog until then. Combined with Memory/sqlite vec retrieval #1, a fresh codex session showed a stale claude list indefinitely — exactly the "never used codex on that machine" case.Fix (client-side, targeted)
fetchModels(provider?, force?)sends the backend on the wire and caches per-backend; the visible catalog tracks the requested backend, with a switch-race guard so a slow response for a backend you already navigated away from is dropped.ModelPickerrefetches (forced) whenever the focused session'sproviderIdchanges; the bare/modelslash fetches the focused session's backend, not the daemon default.A never-used backend still shows an empty catalog until its first turn (the models are genuinely unknown until the backend spawns) — honest, and the picker hides rather than lying. Once it runs once, re-opening the picker populates it.
Why not a daemon fix
listModels()returns empty for every provider until its backend spawns on the first turn — so a daemon "refresh on switch" would have to spawn the backend just to enumerate models, which we don't want. The daemon already caches + persists each backend's catalog after its first turn; the client just has to ask for the right one.Follow-up worth considering: push a models update to attached clients when a backend first reports its catalog, so the picker auto-populates after the first turn without a manual re-open. Filed as a note, not blocking.
Tests
models.test.ts(new, 7): provider sent on the wire, default when omitted, switch swaps the catalog, cached-live served without refetch, not-yet-live refetched, stale-list cleared synchronously on switch, slow-response-after-switch dropped.SessionControls.test.tsx(+2): picker fetches the session's backend on mount; refetches when providerId flips (the reported bug).tsc -b --noEmitclean.The TUI (codeoid-ui) has the same class of bug (its
models.listhas no provider field at all) — fixed in a separate codeoid-ui PR.🤖 Generated with Claude Code