Skip to content

fix(desktop): model chip updates after a switch (session.info republish) - #818

Merged
agentforce314 merged 1 commit into
mainfrom
fix/desktop-model-chip
Aug 8, 2026
Merged

fix(desktop): model chip updates after a switch (session.info republish)#818
agentforce314 merged 1 commit into
mainfrom
fix/desktop-model-chip

Conversation

@agentforce314

Copy link
Copy Markdown
Owner

The bug

Selecting a different provider/model left the composer chip showing the old model — the picker marked the new one, the chip disagreed (screenshot: picker ✓ on GPT-5.6-luna, chip still "Deepseek V4 Pro").

Why

The chip reads the live session's model (PRIMARY_SESSION_VIEW.$model = primaryField(state.model, $currentModel)), not the composer draft. The renderer's own comment states the backend "stamps model/provider on EVERY session.info" and that a switch "publishes session.info when it lands, and that is what re-syncs every surface" — but serve only emitted session.info at system/init, so the session state kept its spawn-time model forever. The optimistic $currentModel paint was shadowed by that stale session state.

The picker/chip disagreement had a second cause: _init_session_info never sent provider, and currentPickerSelection only prefers the session's selection when both model and provider are set — so the picker fell back to the catalog's (correct) current while the chip showed the session's (stale) model.

Fixes

  • _init_session_info now includes provider.
  • New DesktopSession.publish_session_info() — re-reads get_settings and broadcasts a full session.info (model/provider/effort/approval_mode). Awaited after config.set of model/provider/effort, so chip, picker and settings reconcile to the real post-switch state — including a cross-provider switch that lands elsewhere, or fails outright (no false "switched" display).
  • Turn end schedules the same refresh. Scheduled, never awaited from the pump: the control response is routed by the pump, so awaiting there would deadlock until the 30s timeout. Tasks are tracked and cancelled on shutdown.

Verified live (real session, real backend)

  • Chip goes "Deepseek V4 Flash" → "Deepseek Chat" on switch. ✓
  • A failed cross-provider switch (your expired Claude OAuth) correctly keeps showing the real model instead of lying. ✓
  • A full turn completes with the refresh scheduled — no deadlock, zero control timeouts. ✓

4 new pytest cases (provider in init info, switch publishes session.info, turn-end republish + explicit no-deadlock assertion); 69 desktop tests green.

🤖 Generated with Claude Code

Selecting a different provider/model left the composer chip showing the OLD
model — the picker marked the new one, the chip disagreed.

Why: the chip reads the LIVE SESSION's model (PRIMARY_SESSION_VIEW.$model =
primaryField(state.model, $currentModel)), not the composer draft. The
renderer's own comment says the backend 'stamps model/provider on EVERY
session.info' and that a switch 're-syncs every surface' by publishing one —
but serve only ever emitted session.info at system/init, so the session state
kept its spawn-time model forever. The optimistic $currentModel paint was
shadowed by that stale session state.

The picker/chip disagreement had a second cause: _init_session_info never
sent , and currentPickerSelection only prefers the session's
selection when BOTH model and provider are set — so the picker silently fell
back to the catalog's (correct) current while the chip showed the session's
(stale) model.

Fixes:
- _init_session_info now includes provider.
- New DesktopSession.publish_session_info(): re-reads get_settings and
  broadcasts a full session.info (model/provider/effort/approval_mode).
  Awaited after config.set model/provider/effort, so the chip, picker and
  settings all reconcile to the REAL post-switch state — including a
  cross-provider switch that lands somewhere other than requested, or fails
  outright (no false 'switched' display).
- Turn end schedules the same refresh. Scheduled, never awaited from the
  pump: the control response is routed BY the pump, so awaiting there would
  deadlock until the 30s timeout. Tasks are tracked and cancelled on
  shutdown.
- Drive-by: import Any in desktop_audio (used in annotations).

Verified live in the app on a real session: chip goes 'Deepseek V4 Flash' ->
'Deepseek Chat' on switch, and a failed cross-provider switch (expired Claude
OAuth) correctly keeps showing the real model instead of lying. 4 new pytest
cases incl. an explicit no-deadlock turn test; 69 desktop tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@agentforce314
agentforce314 merged commit 1ce5823 into main Aug 8, 2026
3 checks passed
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.

1 participant