Skip to content

[Bug]: Desktop keeps the old model after the same thread changes on mobile #10684

Description

@satyalyadav

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Start a new thread on desktop with model A, either picked in the composer or seeded from sticky/project defaults.
  2. Send a message. The thread runs on A.
  3. Switch the same thread's model to B from the mobile app and send a message there. The server records the change and the phone shows B.
  4. Return to the desktop app and open the same thread.
  5. The composer still shows A, even though the thread is running on B.
  6. Send another message from desktop.

Expected behavior

The desktop composer follows the thread's server-side model after another client changes it. A newer explicit pick made on that client should override it.

Actual behavior

The composer keeps showing A. Step 6 is worse than a stale label: the desktop send path syncs the composer's model back into the thread, so the thread's model silently reverts to A and the turn runs on A. The UI never changes (it was already showing A), so the reversal is invisible to the user.

Evidence from the affected thread

Thread 9954b2b6-9772-43f0-a447-94863a11b15f (OpenCode provider, Linux desktop + mobile). Read-only check of the local server DB, 2026-09-08 UTC:

Time Event Model
03:05:30 thread.created nvidia/moonshotai/kimi-k3 (variant max)
04:29:36 thread.meta-updated, then turn-start-requested opencode/muse-spark-1.3-contributor-free (variant xhigh)
04:30:10 thread.meta-updated, then turn-start-requested asu/glm-5-3-flash

Both model changes came from the phone, 34 seconds apart. The projection's model_selection_json points at asu/glm-5-3-flash and later turns use it. The desktop composer kept showing the original model for the rest of the session.

Where the behavior lives in the code

  • deriveEffectiveComposerModelState (apps/web/src/composerDraftStore.ts) prefers the draft's stored modelSelectionByProvider over threadModelSelection for both the displayed model and the sent model. The modelSelectionExplicit flag does not gate this precedence.
  • For an existing thread the draft is seeded once at thread creation (applyStickyState / setModelSelection in useHandleNewThread) and the seed survives draft promotion. Nothing re-seeds or clears the stored draft selection when thread.meta-updated arrives with a different model. The thread reducer does update thread.modelSelection from that event, but the composer never consults it once the draft holds a stored selection.
  • The send path's persistThreadSettingsForNextTurn (ChatView) compares the composer model with serverThread.modelSelection and issues thread.meta.update when they differ. That is the silent-reversal mechanism.

Impact

Major degradation or frequent failure. The UI lies about which model is running, and the next send from the stale client flips the thread's model with no visible signal.

Version or commit

Current desktop and mobile releases, observed 2026-09-08. Code path verified against current main.

Environment

Linux, T3 Code desktop app and mobile app, OpenCode provider.

Related work

Suggested fix

Treat a stored draft model for an existing thread the way #10205 treats the mobile one: an override that is released once the server confirms its use. When thread.meta-updated changes the thread model, re-seed the stored draft selection unless this client made a newer explicit pick. Keep a pick made on this client after the last confirmed send, and restore it if a send fails.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions