Repository navigation
Description
Summary
Regression / incomplete fix: on Orchestrator v2 nightly, switching model within the same provider instance on mobile does not update the desktop composer for the same thread. The desktop keeps showing the old model and can silently switch the thread back on next send. This is the intra-instance variant of #10684, which was closed COMPLETED by #2829.
Environment
- opencode version: 2.0.22
- OS: Linux hp-laptop 7.0.0-34-generic x86_64
- T3 desktop: 0.0.46-nightly.20261003.2623
- T3 mobile: 2.0.0
- Reporter: @rohitgirdhar (filed via agent triage)
Reproduction
- Open the same T3 thread on phone + desktop, provider
opencode. - Both show model A:
opencode/space-bunny-free. - On phone, switch same thread to model B:
opencode/muse-spark-1.3-contributor-free. - Send "Testing again" from phone — server runs with model B.
- Desktop composer / thread header still shows model A. Persists without manual refresh.
Expected Behavior
Desktop composer follows the server thread model after another client changes it, for both cross-provider and same-instance model changes. A newer explicit pick on that client should still win.
Actual Behavior
Desktop stays on A. Same silent-reversal mechanism documented in #10684: stale composer model gets written back via thread.meta.update on next desktop send.
What was fixed vs what is not
Fixed (per #2829 body): V2 added durable thread.model-selection.set command + thread.model-selection-updated projection replacing per-client sticky state. That covers server durability and the inter-provider handoff path.
NOT fixed: web composer precedence in apps/web/src/composerDraftStore.ts (deriveEffectiveComposerModelState, current main ~L1297-1313):
const instanceSelection = input.selectedInstanceId
? input.draft?.modelSelectionByProvider?.[input.selectedInstanceId]
: undefined;
const activeSelection = instanceSelection ?? legacySelection;
const selectedModel = activeSelection?.model ? /* draft */ : baseModel;baseModel comes from threadModelSelection, but only wins when the draft map has no entry for that selectedInstanceId. space-bunny-free → muse-spark-... share selectedInstanceId = opencode, so the seeded desktop draft (seeded once via applyStickyState, survives promotion + clearComposerContent, per #10684 triage) keeps winning. thread.meta-updated updates thread.modelSelection in threadReducer but nothing re-seeds/clears the draft entry.
#2829's diff to composerDraftStore.ts is +312/-15 adding only threadContexts / stickyOptionsByModelByProvider — no change to this precedence, no thread.model-selection-updated reseed path. So same-instance switches remain stale.
Links
- My report closed as duplicate: mobile-desktop: model switch in phone app doesn't update desktop composer for same thread #15190
- Canonical web-direction tracker closed COMPLETED 2026-10-02 by feat(orchestrator): introduce new orchestrator #2829: [Bug]: Desktop keeps the old model after the same thread changes on mobile #10684
- Mirror mobile-direction: [Bug]: Mobile keeps an old model after the same thread changes on desktop #10202 (mobile-only fix fix(mobile): release confirmed thread model overrides #10205, web not covered)
- Prior wash: Model switched on one client reverts when another client sends #13483 (duplicate of [Bug]: Desktop keeps the old model after the same thread changes on mobile #10684)
- Closer PR: feat(orchestrator): introduce new orchestrator #2829 (merge commit 2026-10-02T19:22:22Z)
Request
Reopen or track as regression of #10684 scoped to same-instance model switch on web: re-seed/clear stored draft selection when thread.model-selection-updated / thread.meta-updated changes the thread model, unless this client made a newer explicit pick (per #10684 suggested fix points 1-4).
Activity
juliusmarminge commentedon Oct 3, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks @rohitgirdhar for the precise report. I confirmed this on current
main(31a9da179e). It's the same web composer behavior as #10684 and is still present. The merged #2829 closed #10684 and #10202 because V2 now stores the thread model on the server, but it didn't change how the desktop composer chooses which model to show and send.What I verified
deriveEffectiveComposerModelStatestill prefers the draft entry for the selected instance and only falls back tothreadModelSelectionwhen that entry is missing:modelSelectionExplicitisn't an input, andmodelSelection.test.tsstill expects a custom-instance draft model to beat a different thread selection.A thread created on desktop gets that draft entry from
applyStickyState/setModelSelection. Promotion copies the whole draft onto the server-thread key (finalizePromotedDraftThread→removeDraftThreadReferences).clearComposerContentclears text and attachments but leavesmodelSelectionByProviderandactiveProvider, and persistence keeps model-only server-thread drafts. Nothing onthread.model-selection-updatedorthread.provider-switchedre-seeds or clears it.resolveComposerProviderSelectionthen triesdraft.activeProviderbefore the thread's instance. OpenCode advertisessupportsProviderSwitchingViaHandoff, so the composer isn't locked to the thread's instance. Your two models share theopencodeinstance, so the seeded draft keeps winning. A desktop that never stored a selection for the thread would followthread.modelSelection, but the usual path (creating the thread or picking a model there) does store one.The next desktop send still writes the stale model back:
startThreadTurnalways sendsmodelSelection: ctxSelectedModelSelection, and if that differs from the thread, the orchestrator records athread.model-selection-updated(orthread.provider-switchedacross instances). Since the picker already shows the old model, that reversal is invisible. Sidebar rows that readthread.modelSelectionshould update; the stale surface is the composer picker and the next send from it.The same precedence also affects switches across providers whenever this client still has a draft for the old instance. Same-instance switches are just the case where the draft entry can never miss.
Related
apps/web.Likely fix area
A maintainer will decide on the fix direction. Options in the same shape as #10205, on the web draft:
thread.modelSelection.thread.model-selection-updatedorthread.provider-switchedchanges the thread model, re-seed unless this client made a newer explicit pick.How to verify
Desktop A → mobile B on the same OpenCode instance → the desktop picker shows B and the next desktop send requests B. Also: an unsent desktop pick C survives a remote B, a failed send keeps C, and reopening the thread doesn't bring back a persisted A.
t3-code-bot commentedon Oct 21, 2026
closing as addressed by #20886, merged into main: web and mobile reconcile confirmed composer model overrides against the live thread state, allowing later model changes from another client to reach the composer without discarding a differing unsent pick.
thanks for reporting this. if you still face it on a build containing this fix, please ask us to reopen this issue or open a new issue linking back here, with your version, os and reproduction steps.
closed at the request of @UtkarshUsername.