Repository navigation
fix(clients): allow portable provider handoffs after detach - #24641
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…thread ref Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…offs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — Allows users to switch providers after detaching a session even when its native thread reference is absent. Existing session-capability, active-run, and source-association safeguards remain, and no provider defaults change. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
Taking over this one |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dismissing prior approval to re-evaluate 4cef96c
EvidenceComplete a Replay todo-list turn, settle the thread, stop the server, clear only its source provider-thread nativeThreadRef, restart, and open a freshly paired incognito tab. Open the model picker and click Codex. Base blocks switching; head permits selecting GPT-6-Luna. Sending a request for the previous assistant sentence completes with the correct historical text and a visible context handoff. Environment: real web client, Chromium incognito, 1280×800 CSS pixels, light theme, separate fresh databases. Both selected stills passed pixel-identical stability checks at least 500 ms apart.
Offline fixture script · Seed commands and browser procedure · Synthetic persisted state evidence Not shown:
|


Problem
A detached thread could not switch providers when the outgoing provider had never returned a native thread reference, even though the server could hand off its saved history.
Change
Allow the existing portable handoff once there is no live session or active run. Keep negotiated session capabilities and the active-run guard authoritative. Web, desktop and mobile consume the shared helper; no server or adapter behavior changes.
Scope and approval
This is a focused fix to the existing provider-switch workflow. Retains the work by @FabioLukas123 in #19687, with the pinned main revision merged and original commits preserved. It adds no providers, model IDs or wire fields.
Verification
Root selected Fixture Handoff Target and sent through the real app pipeline to a copied repository Codex mock peer. The transport record contains thread creation, the exact prior-history marker
T3_19687_CONTEXT_MARKERinthread/inject_items, and a completed subsequent turn. This proves history reached the target transport; it makes no claim about external model comprehension.No external model or network call was made by the mock. Native desktop/mobile shells were not independently exercised. An identical temporary startup-recovery bypass preserved synthetic control rows on both variants; it was removed, the isolated database/settings restored, and the test server stopped.
Current-head CI runs after this update; earlier checks cover the original head only.
Implementation: Claude Fable 5.1. Test assessment and independent review: Claude Opus 5.5. Independent review and coordination: GPT-6.1 Sol, through Claude Code and Codex in T3 Code.
Takeover
Credit to @FabioLukas123 for identifying the detached-session regression in #19687.
Rebuilt on the pinned main revision: remove only the native-reference prerequisite; keep the source provider-thread association so saved history is handed off rather than silently omitted.
The original description above is preserved as historical context; verification below describes this replacement at 271e7b6f11.
Evidence
Before 3eb8366 (merge-base), after 271e7b6 (head). Real web app per checkout, fresh isolated state (
T3CODE_DEV_SEED=0), synthetic data; source completed with sentinelT3_24641_HISTORY, detached through RPC, thennativeThreadRefnulled with the server stopped. Source is mock Grok, target mock Codex. Chromium 156/Linux, no device emulation, DPR 2, frozen clock, settled frames (two identical frames 500 ms apart). Full sheet with every row, diff image and script: Evidence.md, fixture matrix matrix.md.Recordings (open picker → try target → search
gpt-5.6-sol→ select if allowed → send on head): 1440 before / after, 390 before / after. Before stays on the source; after selects the target, shows "Context handoff pending", sends, and the target transport receives the sentinel inthread/inject_itemsand completes (transport record). F02 control (native ref present, head): recording.Guard controls on this same head (session denying handoff, active run, missing source association) are from the earlier round: controls, audited SUFFICIENT.
What else changed: enabled Codex/Claude icons move ahead of the selected Grok icon; the composer label and runtime-control position follow the shorter target name; the existing handoff-pending line appears. Not from this PR: a left-edge "=" glyph in the 1440 after-send still, mock-version notices in the F02 clip, a host-overload toast at the start of the raw 390 before clip (excluded from the stills).
Scripts and fixtures (synthetic, not committed): seed.mjs, client.mjs, null-ref-all.sql, browser-capture-driver.js, diffs.py, all scripts, fixtures, reproduce.md.
Verification log
apps/mobile/src/state/thread-provider-switching.ts, covered by its unit tests.