Skip to content

feat(threads): every fork into a new tab carries the conversation itself - #236

Merged
adampeterhiggins merged 1 commit into
mainfrom
ah/extend-thread-forking-ui
Oct 8, 2026
Merged

adampeterhiggins merged 1 commit into
mainfrom
ah/extend-thread-forking-ui

Conversation

@adampeterhiggins

@adampeterhiggins adampeterhiggins commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Only Fork from this response used upstream's native thread fork. Every other way to fork a chat into a new tab used the fork's summary handoff: Fork into new tab on a user message, the model picker's fork button, the account picker's New tab, the usage-limit banner's Continue in new tab, and mobile Hand off. Those tabs started from a summary chip, not the real conversation with upstream's fork marker.

Fix

  • Server: ForkThreadTabInput takes an optional modelSelection. ThreadTabs.fork runs thread.fork, then thread.model-selection.set on the new tab when the model differs. Upstream's fork already resolves a cross-provider or cross-account fork on the first send by handing over the full conversation, and every adapter supports that.
  • Client runtime: latestThreadForkPoint and threadForkPointBeforeMessage pick the completed response to fork from, including responses inherited from an earlier fork.
  • Web: forking from a user message forks at the response before it and puts the message text and attachments in the new tab's composer. Model and account picker forks fork at the latest finished response on the chosen model and copy the draft across.
  • Mobile: Hand off forks the same way.
  • Fallback: when no response has finished yet (for example, during the first turn), the old summary path is still used.
  • Docs: updated docs/fork-differences.md and the user guides.

Screenshots

Tested in the web app against a copy of real data, on a Claude Opus 5.5 thread.

Fork into new tab on a user message. The new tab carries the conversation through the previous reply under upstream's "Forked from conversation" divider. The message waits in the composer, and the model stays Claude Opus 5.5.

Fork from a user message

Model picker fork onto GPT-6.1-Sol (Codex). The new tab carries the whole conversation through the latest reply and runs GPT-6.1-Sol. The server stored codex as the new tab's provider.

Model picker fork onto Codex

First message in that Codex tab. Upstream hands the Claude conversation to Codex on send: the thread shows a "Context handoff Claude Opus 5.5 → GPT-6.1-Sol" marker, and the stored transfer resolution is portable_context. Codex recalls the previous exchange correctly.

After the first send on Codex

Notes

  • Forking while a turn is running now forks from the last finished response. The old summary included the partial in-progress turn.
  • The subagent "fork with context" action still attaches a summary of the subagent's work, because it isn't a thread fork.
  • Not tested in a real client: the summary fallback (covered by unit tests), the account picker and usage-limit banner (both use the same handler as the model picker), and mobile Hand off.

Verification

  • vp test on apps/server/src/threadTabs/ThreadTabs.test.ts and packages/client-runtime/src/threadTabs.test.ts, both with new tests and both passing.
  • Typecheck is clean for contracts, client-runtime, server, web and mobile.
  • Manual web pass as shown above.

🤖 Generated with Claude Code

Forking from a user message, the model or account picker, the usage-limit
banner and mobile Hand off now use upstream's native thread fork, as
"Fork from this response" already did. A fork onto another model switches
the new tab before its first message, so the conversation is handed to that
provider on send. A summary is used only when no response has finished.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant