Triage
Confirmed as a real mobile composer-state bug, not a duplicate of #6508, #5421, or PR #9372.
What we verified
On current main, the mobile thread composer still prefers a persisted local draft model over the server thread model for both display and the next send:
| 1 | const modelSelection = selectedDraft?.modelSelection ?? selectedThread?.modelSelection ?? null; |
| 1 | const modelSelection = draft.modelSelection ?? thread.modelSelection; |
After enqueue, existing-thread send calls clearComposerDraftContent without clearModelSelection: true. The draft-clear helper keeps modelSelection by default. That retain-after-send behavior is covered by use-composer-drafts.test.ts ("clears sent content without clearing the selected model or workspace").
New-task send already passes clearModelSelection: true. Existing threads do not, so an already-used local pick can keep winning after desktop changes the same thread’s model.
If the user then sends from mobile, outbox drain compares that draft/queued model to thread.modelSelection and will updateThreadMetadata when they differ. That matches the reported later reversal to Sol. The saved events do not name the originating device, so they confirm the reversal, not the device by themselves.
The cited commit d92dca74e is still ancestral; the same precedence is present on current HEAD.
Related work (not duplicates)
- #6508 is about new-thread defaults, not same-thread multi-device sync.
- #5421 is web provider-option precedence.
- PR #9372 updates remembered mobile options for future threads. It does not clear a stale same-thread draft override. Landing it first could even copy that stale model into sticky defaults.
Web already distinguishes explicit vs inherited draft models (modelSelectionExplicit) and strips non-explicit model seeds from empty draft sessions. Mobile thread drafts have no equivalent.
Suggested fix
Treat draft.modelSelection as an unsent override:
- After the server has confirmed the local choice (or after a successful enqueue, if we accept that approximation), drop the thread-draft model so later compose/send follow
thread.modelSelection. - Keep a newer local pick made while a send is still pending.
- Restore the draft model on failed send / outbox recovery.
- Cover empty composer and a draft that still has text.
Also decide whether already-persisted 1.0.3 thread drafts should be stripped on hydrate when they carry only a bare modelSelection (similar to the existing new-task: stale-model strip).
Verification
Need mobile→desktop→mobile on the same thread, plus: next send’s requested model, failed send, deliberate unsent mobile pick, empty vs non-empty draft. Provider: Codex. Connection mode does not change the local draft store.
Labels
bug, accepted, via-triage (remove needs-triage)