Repository navigation
refactor(ssh): main decides when to upload SSH workspaces; downloads go through main (core C5d) - #26320
Closed
Jinwoo-H wants to merge 13 commits into
Closed
refactor(ssh): main decides when to upload SSH workspaces; downloads go through main (core C5d)#26320Jinwoo-H wants to merge 13 commits into
Jinwoo-H wants to merge 13 commits into
Conversation
…es; imports go through main The window no longer uploads to SSH relays. Main exports a host's workspace after any session write whose share for that host differs from what the two last agreed on, and reports each export back to the window as a push-status event. The window's snapshot import now commits its merge through main in one call (remoteWorkspace:importPeerTopology) with the pull's revision, host observation and outcome; that sets the agreement, so an import and the window's save-back of it never export, and a conflicted pull keeps the host out of exports until a whole pull agrees again. The first-connect seed of an empty host is main's export too. Co-Authored-By: Claude <noreply@anthropic.com>
…parking test passes the import seam Co-Authored-By: Claude <noreply@anthropic.com>
…he unavailable retry A pull that lands while an export is in flight supersedes its outcome, so main no longer sends that outcome to the window, where a late stale result could flip a freshly synced target to conflict. Rename the export test driver to the *-test-harness.ts name its neighbours use, which also drops its ratchet row.
… save-back did The window's import outcome gains 'kept-local' when its merge keeps tabs or pending layout edits the host has not seen (and on a never-written host). Main then agrees on the host's own copy instead of what it now holds, so that state uploads once; a plain import still agrees on this desktop's state and never exports back. Replaces the revision-0 special case in main.
Jinwoo-H
changed the base branch from
topo-c5a-pending-panes
to
topo-c5c-sleep-wake
October 7, 2026 22:38
…ade on After a relay restart, main kept the pre-drop agreement and the matching cached host observation, so the first session write after reconnect exported this desktop's view (the dead PTYs' tabs already retired) before the window's pull ran, and the pull then imported the empty host. An agreement now records the connection it was reached on and exports only while that connection is the target's live one; the reconnect pull agrees anew. The push-status event carries only the revision and observation it ran under. Co-Authored-By: Claude <noreply@anthropic.com>
A relay's shutdown kills its PTYs and reports their exits; main retires those panes and exports immediately, so the patch could land on the dying relay and erase the tabs every client restores from its snapshot after reconnecting. The workspace store refuses patches from the start of shutdown (answering unavailable) and reopens if the shutdown defers. Co-Authored-By: Claude <noreply@anthropic.com>
The SSH import sent the merge split across every partition, and main patched each one straight into the store, so a pull could overwrite local and other-host rows with the window's copy — tabs main hadn't created, and main writes the window hadn't received yet rolled back. The window now sends only the `ssh:<targetId>` slice, and main commits it through the topology commit module (`importPeerTopology`, traced like the other commits) into that partition alone. Presentation fields of other worktrees reach main through the window's normal save, as before. Co-Authored-By: Claude <noreply@anthropic.com>
…5d-ssh-import-export
10 of 12 tasks
Contributor
Author
|
Closing: the layout rework is being redesigned so the runtime holds the only copy of the layout and every window/client is a view. This branch is kept for reference. |
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
When you use an SSH workspace on two computers, each Orca uploads its terminal layout to the SSH host so the other one can pick it up. Until now the window (renderer) decided when to upload, after every save. Since the earlier core steps the window also copies main's layout, so a layout the window just downloaded from the host would come back through main and get uploaded again, and the other computer would download it again: an echo. This step moves the upload decision into main (the part of Orca that stores sessions) and has the window hand every download to main in one call, so main can tell "a change made here" from "a change we just downloaded".
This is core step C5d of the terminal layout refactor (C5 is split into a..d). The core steps are reviewed as stacked PRs and merged together as one unit, so
mainnever gets a half-finished version. Stacked on C5a (#26307).What Changed
Main decides when to upload (new
src/main/ipc/remote-workspace-export.ts). For each SSH host, main remembers what it and the host last agreed the host's workspace holds: the host's revision, its observation token, and the exported workspace. After any session write, main rebuilds the export for each agreed, connected host and uploads only if it differs from that agreement. The upload body, the revision/token check before uploading, and the per-host queue are today's code moved out of the oldremoteWorkspace:setForConnectedTargetshandler, unchanged. After a successful upload the agreement moves to the new revision; a "the host moved on" reply drops it until the window's next download.Downloads go through main (
remoteWorkspace:importPeerTopology). The window still runs today's merge (remote-workspace-session-merge.ts, unchanged). It now sends the fields the merge rewrote for that host's ownssh:<targetId>partition only, together with the download's revision, observation token and outcome, in one call. Main commits them through the topology commit module (importPeerTopology, traced like the other commits) into that partition alone, so a download can never touch local or other-host rows or roll back a main write the window has not received yet; the merge's changes to other worktrees reach main through the window's normal save. Main then records the agreement as what it now holds, so neither the import nor the window saving it back causes an upload. Outcomeconflict(the window could not place every host tab) removes the agreement, so that host gets no uploads until a full download agrees again — the same protectionuse-app-session-persistence.tsgave before.First connect to an empty host. The window used to upload its own copy directly (
remote-workspace-target-sync.ts, the second upload path the plan refresh found). Now it reports the revision-0 download to main with nothing to import. Main agrees on the host's empty copy, so anything this computer has for the host is uploaded once, and a computer with nothing for it uploads nothing, as before.Upload results reach the window as a push (
remoteWorkspace:pushStatus). The window applies them as before: sync status, and acknowledging pending layout edits the host now holds. It ignores a report while it is mid-download, in conflict, or looking at a different host observation.Deleted: the window's upload chain in
use-app-session-persistence.ts(about 150 lines), the seed upload, and thesetForConnectedTargetsIPC.patchWorkspaceSessionByHostreturns one promise instead of{ localWrite, written }, since nothing orders an upload after the local write any more.Not changed: what is sent to the SSH host. It is the same
replace-sessionpatch, with the same session shape and the same revision check, so older hosts and older clients on the other end see no difference.remote-workspace-target-session-export.ts(which #26098 changes) is only called, not edited; the one file both PRs touch is a two-line test edit inworkspace-session-ssh-partition-ownership.test.ts.Why
The plan's rule for the SSH echo (R11, D2) is that uploads follow this computer's own changes, never a download or the mirror copy of one. Tagging writes would need a "this write came from an import" flag threaded through the store. Comparing against the last agreement needs no flag: the import sets the agreement, so its write, and any save-back of the same content, compare equal; a split made here doesn't. It also covers main's own writes (for example a PTY binding), which the window-triggered upload only caught when the window next saved. It keeps today's merge semantics, as B2 requires; the additive merge arrives with B3.
Rejected: keeping the seed upload in the window (it is a second upload path, and the brief removes it), and keeping uploads in the window behind a "skip after import" guard (that guard would be window state racing main's writes).
Linked Issue
STA-9417 (fixed by the core as a unit).
Visual Proof
N/A. Nothing on screen changes; this step changes which process decides when to upload an SSH workspace.
Testing
pnpm tc,pnpm run check:code-quality:changed,audit:anti-slop,check:max-lines-ratchetandcheck:reliability-gatespass. CI unit shards 1–5 pass (shard 4 after one rerun of an unrelated agent-launch flake that passes locally 3/3).pnpm test: every file that fails on this machine also fails on C5a (bun/node:sqlitesetup), except two that the second commit fixes: the session-patch boundary ratchet drops main's import handler (the import now writes through the commit module), anddirect-ssh-snapshot-parking.unit.test.tspasses the new import seam. So the first commit alone fails those two tests; the pair passes.remote-workspace-two-desktops.test.ts). Each computer is its own copy of main's modules (own caches and client id), against an in-memory host with revisions. A split on computer A uploads exactly once; B's download of it, and B saving it back, upload nothing; then a split on B uploads exactly once and A's download uploads nothing. A computer whose download was a conflict uploads nothing. Mutation-checked: making the import agree on nothing fails both tests; turning the conflict off fails the conflict test.remote-workspace.test.ts,remote-workspace-patch-queue.test.ts,ssh-host-partition-session-export.test.tsandremote-workspace-stale-resync.test.tsnow drive main's trigger: one catalog read and one ownership check per worktree for all hosts, uploads only to agreed connected hosts, revision/token rejection before and while queued, reset-relay retry, independent hosts in parallel, the empty-host first connect with and without tabs, and an invalid download recording nothing.remote-workspace-snapshot-import-commit.test.ts(the merge goes to main with its download, only the merged fields,conflictwhen tabs can't be placed) andremote-workspace-push-status-event.test.ts(layout-edit acknowledgement, localized fallbacks, error, ignored mid-download/conflict/other host). The deletedremote-workspace-unplaced-upload-suppression.test.tsxcovered the window's upload chain; its guarantees are now tested in main.ssh-host-partition-session-export.test.ts(real store) shows an import leaves the local and other-host partitions byte-identical, including a main-written local tab the window never saw;remote-workspace-snapshot-import-commit.test.tsshows the window sends none of its local rows (fails if the unsplit merge is sent).ssh-docker-reconnect-pane-restore,ssh-lost-kill-tab-resurrection(2),ssh-reattach-home-partition,ssh-reconnect-tab-destructionpass after the change.ssh-cold-activation-restore,ssh-cold-hydration-gap-tab-seeding,ssh-emptied-worktree-reactivation,ssh-reconnect-tab-destruction,ssh-restart-tab-accumulation— 8 passed.pnpm run test:e2e:terminal-layout-parity -- --base 80d45095d2ff --head HEAD, background launch): all 9 scenarios identical to main.Known gaps: