Repository navigation
regression: same-instance (opencode) model switch on mobile still leaves desktop composer stale on Orchestrator v2 #15195
Description
Activity
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:const instanceSelection = input.selectedInstanceId ? input.draft?.modelSelectionByProvider?.[input.selectedInstanceId] : undefined; const activeSelection = instanceSelection ?? legacySelection; const selectedModel = activeSelection?.model ? /* draft */ : baseModel;
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
- [Bug]: Desktop keeps the old model after the same thread changes on mobile #10684 (closed) is this bug, closed by feat(orchestrator): introduce new orchestrator #2829's list on 2026-10-02 without a web composer change.
- mobile-desktop: model switch in phone app doesn't update desktop composer for same thread #15190 (closed as a duplicate of [Bug]: Desktop keeps the old model after the same thread changes on mobile #10684) is an earlier report of the same thing.
- [Bug]: Mobile keeps an old model after the same thread changes on desktop #10202 (closed) is the mirror case (desktop changes, mobile stays stale). Open PR fix(mobile): release confirmed thread model overrides #10205 releases confirmed overrides on mobile and doesn't touch
apps/web.
Likely fix area
A maintainer will decide on the fix direction. Options in the same shape as #10205, on the web draft:
- After the server confirms this client's choice, drop or re-seed the thread-draft model so later sends follow
thread.modelSelection. - When
thread.model-selection-updatedorthread.provider-switchedchanges the thread model, re-seed unless this client made a newer explicit pick. - Keep a pick made here after the last confirmed send, including a same-value re-pick, and restore it if the send fails.
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.
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 3, 2026
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
Reproduction
opencode.opencode/space-bunny-free.opencode/muse-spark-1.3-contributor-free.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.updateon next desktop send.What was fixed vs what is not
Fixed (per #2829 body): V2 added durable
thread.model-selection.setcommand +thread.model-selection-updatedprojection 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, currentmain~L1297-1313):baseModelcomes fromthreadModelSelection, but only wins when the draft map has no entry for thatselectedInstanceId.space-bunny-free→muse-spark-...shareselectedInstanceId = opencode, so the seeded desktop draft (seeded once viaapplyStickyState, survives promotion +clearComposerContent, per #10684 triage) keeps winning.thread.meta-updatedupdatesthread.modelSelectioninthreadReducerbut nothing re-seeds/clears the draft entry.#2829's diff to
composerDraftStore.tsis +312/-15 adding onlythreadContexts/stickyOptionsByModelByProvider— no change to this precedence, nothread.model-selection-updatedreseed path. So same-instance switches remain stale.Links
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-updatedchanges the thread model, unless this client made a newer explicit pick (per #10684 suggested fix points 1-4).