pingdotgg / pingdotgg/t3code

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

Open
#10,684 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

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
  • #10202 is the mirror case (desktop changes, mobile goes stale) and is triage-confirmed. PR #10205 fixes the mobile draft store. The web direction is not covered by that fix.
  • #5421 is about sticky provider options on web and is explicitly scoped away from the model.
  • #6508 is about new-thread defaults, not same-thread changes from another client.
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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in apps/web/src/composerDraftStore.ts with deriveEffectiveComposerModelState, then trace applyStickyState, setModelSelection in useHandleNewThread, and thread.meta-updated handling. Read ChatView's persistThreadSettingsForNextTurn to understand the reversal path. Done means the desktop composer follows confirmed server changes, preserves a newer explicit local pick, and does not silently revert the thread on send.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.