Skip to content

fix(clients): allow portable provider handoffs after detach - #24641

Merged
juliusmarminge merged 7 commits into
mainfrom
takeover/pr-19687
Nov 14, 2026
Merged

juliusmarminge merged 7 commits into
mainfrom
takeover/pr-19687

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

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

  • The regression is one changed assertion in an existing test. It fails without the fix; ten redundant PR-added cases were removed without deleting main tests.
  • 22 focused shared/mobile tests pass. Existing tests still reject active runs and sessions that cannot hand off; a guard-removal probe makes them fail.
  • 11 server planner tests and five existing portable-text-handoff integration scenarios pass. An independent review also passed 170 targeted shared/mobile/server/web tests. Those existing integration scenarios cover the transfer path; the live fixture below covers the missing-native-reference case.
  • Targeted lint, formatting and client-runtime typecheck pass. Independent Opus and Sol full-diff reviews found no blockers.
  • Root compared 952921f39a with 798ab31708 in the actual web app using a synthetic detached thread with no native reference. The alternate provider is disabled before and enabled after. A session denying handoff and an active run each keep it disabled.
Before: alternate provider disabled After: alternate provider enabled
Before: detached thread cannot select another provider After: detached thread can select another provider
Control: live session denies handoff Control: active run blocks switching
Live-session control remains disabled Active-run control remains disabled

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_MARKER in thread/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 sentinel T3_24641_HISTORY, detached through RPC, then nativeThreadRef nulled 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.

F01 detached, null native ref Before 3eb8366 After 271e7b6 Diff0 Diff16
Open picker, 1440×1000 light before after diff0 diff16
Composer, 1440×1000 light before after diff0 diff16
Open picker, 390×844 dark before after diff0 diff16
Composer, 390×844 dark before after diff0 diff16

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 in thread/inject_items and 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
  • No code changes this round; head unchanged at 271e7b6. Tests as described above (regression assertion fails without the fix).
  • Earlier rounds: two review rounds, final round both MERGE; proof audit pass 3 SUFFICIENT for web at this head.
  • This round: proof audit pass 1 derived a 15-row matrix; rows exercising picker layout only (long lists, unavailable source, keyboard routes, awaiting projection) are not affected by this diff. Captured F01 at 1440 light and 390 dark with history delivery, and F02. Proof audit pass 2 not run this round (time budget).
  • Unverified: native mobile (Android Pixel 9, iOS iPhone 17 Pro). The Android dev client build did not finish in budget and the iOS simulator build failed on a missing GhosttyKit (error). Mobile consumes the same shared helper via apps/mobile/src/state/thread-provider-switching.ts, covered by its unit tests.
  • CI: all 39 checks green on this head.
  • Models/harness: Claude Opus 5.5 lead; GPT-6.1 Sol proof audit and capture, through T3 Code Claude and Codex harnesses.

juliusmarminge and others added 5 commits October 25, 2026 00:13
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>
@github-actions github-actions Bot added vouch:trusted Author is trusted by repo permissions or the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Oct 25, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Oct 25, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 4cef96c

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.

@juliusmarminge juliusmarminge added bugfix Fix PR opened by an issue lead; enters the PR loop pr:gate-failed A land gate failed labels Oct 25, 2026
@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The provider handoff eligibility check no longer requires a native thread reference on the active provider thread. The test now expects eligibility when that reference is absent and verifies that an empty provider thread list does not qualify.

Changes: Provider handoff eligibility

Layer / File(s) Summary
Update handoff eligibility and test
packages/client-runtime/src/state/threadWorkflows.ts, packages/client-runtime/src/state/threadWorkflows.test.ts
A matching active provider thread can qualify for handoff without a native thread reference. The test covers this case and verifies that an empty provider thread list does not qualify.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: t3dotgg, yash-singh1

Merge Risk: Minimal · up to 4cef9

Detached threads can now hand off their saved history to a different provider even when the previous provider has no native thread reference. Session capability and active-run guards are unchanged, and no merge-blocking risk was found.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: detached threads can now hand off portable provider history after detach. It uses a conventional commit format. The changeset removes the native-thread-reference prere…
Description check Passed The description follows the repository template. It has Problem, Change, Scope, and Verification sections with substantive content. Scope links issue #19687 and explains the takeover. Verification giv…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: detached threads can now hand off portable provider history after detach. It uses a conventional commit format. The changeset removes the native-thread-reference prerequisite in threadSupportsProviderHandoff and updates the matching test assertion, which fits the title.

Full details: Description check

Explanation

The description follows the repository template. It has Problem, Change, Scope, and Verification sections with substantive content. Scope links issue #19687 and explains the takeover. Verification gives specific test counts, a before/after regression result, and live evidence. Minor gaps: the template asks for focused tests and states what could not be checked, and the author did state unverified native mobile shells. The description is also long and includes material outside the template's intent, such as evidence hosted on external links and a long verification log. These do not make it incomplete.

Finishing Touches

Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 271e7b6 and 4cef96c.

Files selected for processing: 2

  • packages/client-runtime/src/state/threadWorkflows.test.ts
  • packages/client-runtime/src/state/threadWorkflows.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

@juliusmarminge juliusmarminge added pr:in-progress A lead is working on this PR and removed pr:gate-failed A land gate failed labels Oct 27, 2026

Copy link
Copy Markdown
Member Author

Taking over this one

@juliusmarminge juliusmarminge self-assigned this Oct 27, 2026
@juliusmarminge juliusmarminge added pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked and removed pr:in-progress A lead is working on this PR labels Oct 27, 2026
@juliusmarminge juliusmarminge added the takeover:in-progress A takeover lead is working on this PR label Nov 14, 2026
@macroscopeapp
macroscopeapp Bot dismissed their stale review November 14, 2026 07:31

Dismissing prior approval to re-evaluate 4cef96c

@juliusmarminge

Copy link
Copy Markdown
Member Author

Evidence

Complete 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.

Before After
25c5032b3b 4cef96c259
Before: Codex disabled after detach with missing native reference After: Luna receives prior history and completes its reply
Picker interaction recording Enabled picker recording, send and response recording

Offline fixture script · Seed commands and browser procedure · Synthetic persisted state evidence

Not shown:

  • A natural user flow producing the missing native reference was not established; the reference was cleared offline by hand, matching triage.
  • Mobile and Electron were not exercised; this proof covers the real web client.
  • Sending after switching on base could not run because the picker blocks switching; no bypass was used.

@juliusmarminge juliusmarminge added takeover:ready Takeover done; auto-lands when gates pass and removed takeover:in-progress A takeover lead is working on this PR labels Nov 14, 2026
@juliusmarminge
juliusmarminge merged commit a81ec88 into main Nov 14, 2026
53 checks passed
@juliusmarminge
juliusmarminge deleted the takeover/pr-19687 branch November 14, 2026 07:51
@juliusmarminge juliusmarminge removed the takeover:ready Takeover done; auto-lands when gates pass label Nov 14, 2026
Pipyakas pushed a commit to Pipyakas/t3code that referenced this pull request Nov 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop pr:needs-julius Needs Julius: route gate, unverifiable surface, or blocked size:XS 0-9 changed lines (additions + deletions). vouch:trusted Author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant