Repository navigation
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… capability Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…b client Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…space Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent commit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Give SshGitWorkingTreeProvider.carryWorkingTreeChanges a 120s timeout (a carry can move up to 2,000 files / 200MB, past the mux's ~30s default), and when the request times out or the connection drops, report partially_applied with the error detail instead of apply_failed - the host may have written some or all of the changes, so "nothing was carried" would be a lie (docs/reference/ssh-execution-boundary.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…us before forking Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a "Fork Agent Session…" item to the single-worktree context menu, above "Set Parent Worktree…", that opens the global fork dialog pre-scoped to the right-clicked workspace. Hidden for folder workspaces; disabled while deleting or when the worktree has no branch, is archived, or is bare. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
"Fork Agent Session…" used the U+2026 ellipsis character; every sibling
label in this menu ("Set Parent Worktree...") and the existing terminal
item ("Fork Agent Session...") use three ASCII periods instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tive chat Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rtain Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ollback inside the target - Verify the stash's parent (or the source HEAD when nothing is tracked) still equals the target HEAD after `stash create`, so a commit made between the base check and the stash returns base_mismatch instead of silently dropping that commit's edits. - A failing `stash create` (e.g. an intent-to-add entry) now returns apply_failed with git's message instead of throwing. - Create the temporary stash with a fixed -c identity for older Git hosts without user.email. - Roll back by removing carried paths before `reset --hard`, record an untracked copy only once it is written (or partially written), and never remove a path whose copy failed with EEXIST. - Refuse to copy, remove or count as free any path behind a symlinked target directory: stash create does not record a tracked symlink replaced by a directory, so the copy used to write through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uncertain The carry rejection classifier listed the lost-contact codes it knew and called everything else "nothing written", so a manual disconnect, an unreadable response or a plain transport error reported a clean failure even though the host may have run the carry. Flip it to an allowlist of refusals known to happen before dispatch (queue overload, unknown method, invalid arguments, selector/auth refusals, a request released before it was sent, an older SSH relay without the handler); anything else is reported as possibly partially applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…us probe fails The web client told the user the paired server needed an update whenever its capability probe failed, including when the probe never reached the host at all. Report an unreachable host separately; the update message is kept for a host that answered without the carry capability. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… own command Split a resume command line at shell separators and parse each invocation, so a --fork-session flag only exempts the claude invocation it belongs to and a chained plain resume of an owned session is still refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a pane Only hand a fork the sleeping record's or launch registry's config when it belongs to the same agent and provider session; a pane that ran Claude then Codex otherwise forked Claude with Codex's command and env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aunch Catch a thrown agent launch after the child workspace exists, record it as agent-not-started and reseed the child, so the dialog reports the fork instead of a failure that a retry would turn into a second workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve the child's repo by host instead of by bare repo id when choosing the fork command's platform and shell, so a local repo that shares an SSH repo's id no longer makes an SSH fork get Windows quoting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… workspaces When a remote Orca runtime owns the child workspace, create the fork tab through the host like every other launch, with the fork command quoted for the host's verified platform rather than the client's. The fork still makes no resume claim, so the source session keeps its owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an applied stash type-changes a folder to a symlink, the copy guard refused the next carried path but threw an untagged error, so the carry recorded it as written; rollback then hit the same guard before `reset --hard`, left the stash applied and reported partially_applied. Tag the guard error with CARRY_SYMLINKED_ANCESTOR so, like EEXIST, it is never recorded or removed and rollback restores the target. The pre-write check now uses a dedicated findSymlinkedAncestor io hook and names the blocked path in the target_dirty detail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The disabled Fork Agent Session item put its reason in a title attribute, which never shows because disabled menu items ignore the pointer. Wrap it in a tooltip like the primary Delete Worktree item, and disable it with a reason when the workspace's project record is missing, matching the terminal entry's gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The not-carried toast named only a generic reason, so failures like a carried path behind a symlinked folder or a failing git add -N gave the user nothing to act on. The fork outcome now keeps the host's detail and the toast shows it, truncated, on a second line under the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Forking from a terminal pane without a recognised agent dropped its captured context, so the dialog lost Copy context, and an empty scrollback refused the fork outright. The modal data now keeps the context with a null agent: the dialog offers Copy context for it but no transcript launch. An empty pane opens the dialog for a branch-only fork instead of the 'No terminal context to fork' error; the source checks (floating terminal, folder, detached, archived, bare, unknown) still refuse. The dialog tests share their helpers through a test fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion appears When a terminal pane forked by transcript reported its native session while the dialog was open, the transcript option disappeared and the selection fell to 'No agent'. A vanished transcript selection now moves to that pane's native session, and the session picker ignores the empty value Radix reports while the selected item is swapped out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ames The dialog kept saying 'Start a new branch from <workspace>' after Advanced picked the project default or another ref; it now drops the parent's name for any other base. Workspace names interpolated into the description, the child switch label and the carry note now sit in wrapping spans, so one long unbroken name no longer overflows the dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dialog re-reads it If the parent's status read at submit failed, the fork went out from the branch with no changes carried even though the switch was on. It now falls back to the dialog's last successful read; the carry still refuses if HEAD moved since. A slow status read from when the dialog opened can no longer overwrite the fresher one taken at submit. The reads move into their own hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s replaced The orca.yaml trust prompt can replace the fork dialog mid-flow, which hid the remaining progress and let the user start a second fork of the same workspace. Once the dialog is gone, the fork's stages now show in a loading toast that is dismissed on success or turned into the error on failure. A fork claims its source workspace until it finishes, and another fork of that workspace shows an inline 'already being created' error instead of starting; a dialog closed before the fork starts frees the claim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fork menu item now looks up its project through the store's repos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…parse A command with command substitution or an unquoted backslash escape can run invocations the separator split does not see, so such a command no longer gets the --fork-session exemption and is also read whole, as before forks existed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Send viewMode 'terminal' with a runtime-owned fork, as the host-published new tab launch does, so a host that defaults Claude to chat view still opens the fork's terminal command in a terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch config Check a fork's registry launch config with the store's own stale-proof rules, so a config tied to a launch token or terminal handle the session cannot prove falls back to the user's defaults instead of another launch's account env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ession Forking from an empty terminal pane, or one whose agent is unknown, left nothing for that pane in the session list, so the dialog preselected the first other pane's session. With exactly one such session the picker was hidden too, and submitting silently forked a different conversation. A pane with nothing to fork now preselects 'No agent' and always shows the picker, so choosing another pane's session is deliberate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Arrow keys skip disabled menu items, so the tooltip added for the disabled Fork Agent Session item never reached keyboard or screen-reader users. The reason is now a muted second line inside the item, as in the review-notes send menu, and is part of its accessible text; the tooltip is gone. The item also decides 'folder workspace, hide it' from the same host-aware project lookup as the terminal entry, instead of the card's bare-id repo, so the now-unused repo prop is dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p exemption Treat a backslash or cmd caret as unmodelable only before a quote or separator, so a plain Windows override path keeps the fork exemption. Also withhold the exemption for ANSI-C quoting, process substitution, a backslash quote inside double quotes, an unterminated quote, and any chained command, while substitution inside single quotes stays literal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut a cwd prompt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lder trust Upstream stablyai#23744 moved agent folder trust out of the renderer: the agentTrust:markTrusted IPC and preflightAgentTrust are gone, and the PTY spawn builders (and the relay on SSH hosts) now pre-trust the workspace for any fresh launch that declares its launchAgent, behind the agent-wide setting. The fork launcher still called the deleted preflight. Drop it for both the native and transcript forks; both already declare launchAgent on the tab, the pending startup, the runtime-host terminal and launchAgentInNewTab, so the child worktree is trusted on the host that runs the agent, exactly like every other Orca agent launch. The worktreePath and connectionId the native launcher took only for the preflight go with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mable agents Upstream added codebuddy, qoder and dsh to RESUMABLE_TUI_AGENTS, so the exhaustive switch no longer typechecked. None has a native fork command yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
You are chatting with Claude or Codex in a workspace and want to try something else without losing that line of work. Right-click the workspace, pick Fork Agent Session..., give the new branch a name, and Orca makes a new workspace nested under the current one. It starts from the exact same code, optionally including your uncommitted changes, and the same agent continues the same conversation there. The original workspace and its conversation are left untouched.
What Changed
Before: branching agent work took three manual steps:
The existing terminal Fork Agent Session... only pasted a capped copy of the terminal scrollback into a fresh agent. It created a top-level workspace from the branch tip and left uncommitted changes behind.
After:
claude --resume <id> --fork-session. Codex runscodex fork -c tui.resume_cwd=current <id>. Both get a brand-new session id, so the parent's session keeps its own writer.fork), UserPromptSubmit and Stop all report the new id.orca servebehind a capability):git stash create/git stash apply(binary-safe, Git ≥ 2.25). Untracked files are copied with the filesystem.ssh-execution-boundary.md).orca.yamltrust confirmation as every other create path. Folder trust for the agent comes from the existing launch-time trust (feat(agents): pre-trust the folder wherever Orca starts an agent #23744).claude --resume X --fork-sessionthrough, but only as a single command it can fully parse. Chained, substituted or otherwise unparseable commands keep the guard.Why
This is the stacked-work flow #10849 asks for, plus the part the terminal fork never had: the agent actually remembers the conversation.
Alternatives considered:
git diff+git apply). Rejected: stdin is text-only in both the local runner and the relay, andGIT_INDEX_FILEdoesn't cross into WSL.stash create/applymoves the changes as git objects and is binary-safe.How to review
98 files, but about 58% of the lines are tests and many production files are 1–10 line registrations. Suggested order, smallest to largest:
src/shared/agent-session-fork-argv.ts: the fork command per agent.mode: 'fork'option insrc/shared/tui-agent-resume-startup.ts.--fork-sessionexemption insrc/main/ai-vault/structured-session-ownership.ts.src/renderer/src/lib/agent-session-fork-flow.tsruns the steps: trust/setup decision → create the child at the parent commit → carry → launch → activate. It's the best single file to read first.agent-session-fork-launch.ts(+-launch-target,agent-fork-launch-platform),worktree-agent-fork-sessions.ts(which sessions can be forked),agent-session-fork-setup-decision.tsandagent-session-fork-source-repo.ts.src/renderer/src/components/agent-session-fork/holds the dialog, its state hook, and the status probe.sidebar/WorktreeForkAgentSessionMenuItem.tsxis the sidebar item (one line in the menu view).terminal-pane/*now opens the shared dialog; the old dialog is deleted.AppRootSurfaces.tsx.CreateFromPicker.tsxandSettingsFormControls.tsxonly gain optional a11y props, so existing callers are unchanged.src/shared/working-tree-change-carry.tsis the whole algorithm; it is pure, with injected I/O and tested against real repos.working-tree-change-carry-node-fs.tsholds the filesystem side.main/git/source-control/carry-working-tree-changes.tsand the IPC handler infilesystem-git-index-handlers.tsssh-git-working-tree-provider.ts, plusrelay/git-handler-working-tree-carry.tsruntime-git-working-tree-carry-commands.tsand thegit.carryWorkingTreeChangesRPC, behind a capability inprotocol-version.tsruntime-git-working-tree-carry-client.tsandweb-git-carry-api.tsThe commits are split by topic in the same order, so the PR can also be reviewed commit by commit.
Linked Issue
Fixes #10849
Related:
Visual Proof
Screenshots use the Spanish locale; a video of the full flow follows.
Testing
pnpm dev, real Claude Code 2.1.282 and Codex 0.159.3:git add -N, size caps, and the source left untouched.pnpm typecheck,pnpm lintandcheck:code-quality:changedpass.pnpm build:desktopbuilds the relay, the CLI, electron-vite and the web client. The mobile web bundle step was skipped locally because themobile/workspace dependencies aren't installed in my checkout; this PR doesn't touchmobile/, and CI covers it.pnpm testpasses for everything this PR touches. The only failures are in files it doesn't touch and fail the same onmainin my environment: SQLite FTS5 under Node 22, real-binary integration tests, and cross-version tags missing in a fork.AI Disclosure
Built with Claude Code (Claude Opus 5.5, plus Claude Sonnet 5 for some implementation steps).
Review
The AI code review explicitly covered:
path.join, and quoting per shell (POSIX, PowerShell, cmd). The symlink copy is guarded and fails honestly on Windows without symlink privilege.STYLEGUIDE.mdand existing primitives (Dialog,Switch,Collapsible,CreateFromPicker). Strings are in all six locales.orca.yamltrust gate is kept, and the session ownership guard was not weakened.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
--no-daemonin Codex's launch args as a workaround. Claude is not affected.codex forkdoesn't pin the source session's account home.wait-for-setuprepos the agent starts alongside setup, and with theasksetup policy the fork is created without setup.Author
X: @totigm8
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred). Local exceptions are listed under Testing; CI covers them.🤖 Generated with Claude Code