* perf(ci): accelerate adhoc release builds and runner scheduling
* Overlap adhoc release preparation and reuse qualified build inputs
* Bind qualified template reuse to its embedded CLI source commit
* Use the available CPUs for native compilation in fast dev builds
* Verify qualified template cache provenance and daily invalidation
* Avoid incremental MSBuild tracking for fresh fast-release builds
* Right-size Intel macOS and Windows ARM release qualification
* Route Intel qualification through the standard larger macOS label
* Overlap Windows preparation and initialize the server native compiler once
* Test server compiler invocation through its process-runner interface
* Align adhoc signing contract with its larger packaging runner
* Recognize the native cache fingerprint as batch-file metadata
* perf(serve): copy the supervisor's code aside with one cp clone on macOS
orca serve --supervise copies the app's unpacked code and node_modules out
of the app before re-running itself from the copy. Node's fs.cp clones those
~6,000 files one at a time, which took 8-15 s in the foreground and around
35 s under the service manager's throttled I/O. One /bin/cp -cR per tree
does the same copy in about 1.5 s.
The clone-then-plain-copy fallback the terminal host copy already used now
lives in src/shared/macos-tree-clone.ts, and both copies call it. Linux keeps
fs.cp with reflink cloning, and Windows does not support --supervise.
Claude-Session: serve-snapshot-clone
* fix(serve): let the macOS tree copy run without a time limit and throw on failure
The shared cp helper ran under runProcess's 30 s default time limit, which the old fs.cp copy never had. At background I/O priority a large tree can outlast it, so the supervisor would quietly give up and run from the app itself. The helper now runs cp with no limit; the terminal helper copy stays bounded by its deadline signal.
The helper moves to src/main beside the asar-safe rm and uses it directly, so it no longer takes an rm option. It throws instead of returning a boolean, so copyBundle is gone. The relocation reads process.platform instead of a platform dependency, and its copy test now runs on every platform but Windows.
Claude-Session: serve-snapshot-clone
* fix(build): keep the macOS tree copy module where the packaged CLI loads it
The serve supervisor's relocation code in the CLI now imports the macOS
tree copy module from the main-process sources. The CLI is compiled file by
file, but the later main-process build replaces out/main and keeps only the
files it is told to build as entries, so the packaged CLI failed at load
with "Cannot find module '../../main/macos-tree-clone'" on every platform,
and the CLI runtime verifier rejected the build.
Register the module as a CLI main entry, like the other main modules the
CLI imports, and unpack it from the app archive. Its asar-safe removal
dependency is bundled into a shared main chunk, which is already unpacked.
The build output contract test now covers both.
Claude-Session: serve-snapshot-clone
* fix(build): unpack the asar-safe rm the CLI's tree clone imports
The compiled CLI layout keeps out/main/macos-tree-clone.js requiring
./asar-transparent-fs directly, and that file was still packed, so the
packaged CLI failed to load (caught by packaged-cli-assets in CI). The
bundled build only passed because the main build inlines it into an
unpacked chunk.
Claude-Session: serve-snapshot-clone
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)
A records frame no longer re-serializes the window's whole session and runs the
Loader. Panes' terminal bindings and live titles apply alone, to the tabs the
frame names; any other record builds only its own workspace's slices, with the
view and facts read from the store. Only a workspace the store does not show
from frames yet (startup, a reopened stream, a first visit, one sent whole)
takes the full build.
Tests install an apply check that, after every flush, compares the store with
the full build for the same cache and pre-flush store; the shared layout-apply
transport now sends main's records frames and fails a flush that differs.
* A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only)
* The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only)
* The account-restart test types its store writes, so its five old e2e type errors are gone (test only)
* A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only)
* A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only)
* The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only)
* The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only)
* A tab move shows from main's frame alone; the window no longer re-applies it or adopts main's group id (dark, layout-owner build only)
On main's answer the window only selects the moved tab in its new group, a view choice the
frame does not carry. While main's stream is down it still moves the tab itself, as the frame
sync's closed-stream rule says, but under its own group id: the reopened stream's snapshot
redraws main's copy.
The three switch-on census files for tab moves now pass without a switch mock: the context
menu's move routes to the tab bar's move, the drag hook's pane-column split goes to main in a
layout-owner build, and the tab bar's own move is pinned on a paired server's workspace. A new
frame test pins each move through main and its frame.
* A tab's rename, color, pin, view and a pane's title show from main's frame alone; the window no longer applies them again (dark, layout-owner build only)
* A divider drag sends main the ratios it moved, not the window's tree, and an in-tab pane move shows from main's frame; the ratio hold is gone (dark, layout-owner build only)
* Divider ratio params and their command tests get their own modules (file size)
* Layout-writer baseline pruned: the tab move no longer writes the layout from the window
* The window never draws a tab prop, pane title or in-tab move itself; while main's stream is down the reconnect's snapshot shows them (dark, layout-owner build only)
* View-mode test seeds its paired-server workspace without a type assertion
* A moved tab is selected in the frame flush that shows the move, unless the user selected since; main's answer no longer selects it (L120, dark, layout-owner build only)
* Frame sync stays within its line budget
* Frame sync writes its patches through the moved-tab selection, within its line budget
* The moved tab's selection reuses activateTab's own store update, so no new layout writer is added (L120)
* Format the tab-move frame test
* A tab moved while main's stream is down is selected by the reconnect's snapshot that shows the move (L120)
* A generated tab title main gave is drawn by its frame alone; the window writes only titles left to it as today, and the rename test checks the phone graph republishes (dark, layout-owner build only)
* A divider ratio names the split it means, and main refuses it where that path holds another, so the window snaps back to main's ratios; a drag back to main's ratio before it answers is sent too (dark, layout-owner build only)
* test(layout-owner): tab moves drawn from frames make no window tab or group writes
* test(layout-owner): a reopened tab shows its title and color from main's frame
* test(layout-owner): the tab bar's preview pin through main ends the preview and counts a use
* test(layout-owner): read main's generated-title renames without a type assertion
* test(layout-owner): type the sent renames so the title helper reads them without an assertion
* The frame-apply check selects the moved tabs the flush selected, so the tab-move tests exit clean and a wrong selection still fails (dark, layout-owner build only)
* test(layout-owner): the title batch's live labels are checked in both builds; only the generated title differs
* Main keeps a tab's first agent-generated title unless the window's title replaces it, so a second prompt while main's stream is down no longer renames the tab (dark, layout-owner build only)
* test(layout-owner): a revealed terminal's tab name is drawn from main's frame, not its reply (dark, layout-owner build only)
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(process-host): let a slow Windows taskkill finish instead of orphaning the grandchild
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(process-host): abandon taskkill shortly after the root exits so it cannot walk a reissued pid
Co-Authored-By: Claude <noreply@anthropic.com>
* test(process-host): log the taskkill outcome and caller settle time under load
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: register the late-taskkill Windows test in the package-windows path list
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(process-host): release the git admission slot on root close again; cap the background taskkill at 30 s
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
tsconfig.cli.json listed 284 src/main files because composite made any
unlisted import fail the parallel typecheck with TS6307. Nothing references
it as a project, so drop composite and keep only the CLI's roots plus the
backup worker entry, which is loaded by path. The Electron import ratchet now
also walks the CLI entry, replacing the fence the list implied.
* refactor(native-chat): remove three chat mechanisms nothing drives
- The adapter's optional startAnswered hook lost its only implementer in
#27925; it was still forwarded through four layers and always read as
answered. A start that never answered is read from the child's phase, as
the exit already does.
- The saved lease's journalCheckpoint is only ever written as null and read
only by validation. Records an older build saved with it still load: it
joins the retired lease fields dropped at load, so the next write sheds it.
- The record store's setConversationCommand had no production caller. The
tests that stood in for an older build with it now seed the saved row and
reopen the store; three that only re-pinned another test are removed.
* test(native-chat): an older build's null journal checkpoint also loads
* Keep startAnswered cleanup in its owning PR
* Keep historical chat fixtures aligned with their readers
* fix(chat): preserve Claude rewind context after rollback
Fixes STA-11420.
* fix(lint): merge duplicate journal-types import
* fix(chat): skip torn Claude transcript lines when materializing a rewind fork
* fix(editor): name a server's file by its owned id from the first open, so it no longer reloads after a restart
* refactor(mirror): a server file's mirror id no longer needs the bare-path collision lookup
* fix(editor): conflict review reuses an open server file under its owned id instead of opening a second copy
* fix(editor): conflict review overview shows an open server file's unsaved edits under its owned id
* fix(editor): route conflict files by their workspace owner only; keep the overview lookup a plain read
* test(mirror): the browser-placement replay test names its server file by the owned id
* feat(ci): ratchet raw repo connectionId host reads; one drop-in resolver
Codex review 6 class fix (#4/#6/#7): host decisions read off a repo row's
legacy connectionId miss rows that name SSH only as executionHostId.
- getRepoRoutingConnectionId (shared/execution-host.ts): drop-in for a raw
repo.connectionId routing check that also finds executionHostId-only SSH.
- check-repo-host-read-ratchet: per-file count of raw repo connectionId
reads in src/main, src/shared, src/cli; counts may only shrink.
- Migrated 66 cheap reads: runtime GitHub/GitLab commands, PR/MR head ref
fetches, preserved-branch cleanup, ref queries, base-ref IPC, worktree
preparation, trash sweep, scan cache TTL, usage metadata.
* fix(ipc): PR base IPC routes executionHostId-only SSH rows; ratchet is a stated lower bound
Review follow-up (Opus on #31096):
- worktrees:resolvePrBase decided git/gh routing on the raw connectionId while
its head-ref fetches already used the resolver; one resolved connectionId
now drives all of it (test fails on main).
- getRepoRoutingConnectionId's contract is stated exactly: never local when the
raw field is set; on a contradictory ssh row executionHostId wins.
- The ratchet regex no longer matches report.connectionId; header and docs
say it is a lower bound.
- Regression tests for a runtime GitHub and GitLab command site.
* test(ci): pin the repo host-read ratchet step to the static phase
* fix(ci): repo host-read ratchet skips verbatim field copies into projected rows
A copy of connectionId into a projected row decides nothing; migrating it to
the resolver would change what the projection publishes. The sidebar feed
catalog's copy (added after the baseline) and two older projections no longer
count; baseline pruned to 91.
* fix(native-chat): rewind works at any chat length
Rewind copied the kept part of the chat into its saved record and, for
Codex, re-read and re-translated the whole provider history before and
after the revert. Both were bounded (10,000 rows / 2 MB host, 1,024
entries / 2 MB Codex), so long chats were refused with "too long to go
back in".
The record now names the cut; the kept rows are derived from the
journal, which keeps the old epoch until the rewind completes. Codex
reads only the turns it undoes and trusts thread/revert; recovery
proves an applied revert from the newest turn alone and never
truncates otherwise.
* feat(native-chat): Claude chats launch a rewind's fork of the kept conversation
A rewind record can name the provider session the chat continues in (providerFork). Claude's next
launch forks it with the SDK's own truncating resume at the rewound prompt's parent, and publishes a
forked link once the provider has created it. Removes the transcript proof branch that could never
pass on the real CLI.
* test(native-chat): pin Claude's rewind fork against the real CLI
* feat(native-chat): rewind Claude chats
Claude declares rewind. The adapter answers where a rewind forks (the rewound prompt's parent in
its own transcript); the host saves that fork with the prepared record, stops the agent, and
rebuilds the journal from the rows before the message. Recovery completes a prepared fork, since
nothing outside Orca can have half-happened, and the next launch makes the fork.
* test(native-chat): keep the Claude rewind tests to the flows they pin
* test(codex): type the rewind fixture's page params instead of asserting them
* ci: run the PR checks against main
* fix(native-chat): rewind recovery reads the provider handle only from a record that exists
* fix(native-chat): import the session record type once
* test: run the rewind cut test in the SQLite runtime project
* test(native-chat): run the fork rewind test on SQLite, and give Claude's test double its fork
* fix(native-chat): omit rejected submissions from rewound history
* fix(native-chat): rewind forked chats without restarting the agent
* fix(native-chat): Claude rewinds also leave out messages that never ran
* fix(native-chat): Claude rewind to a mid-turn message drops the rest of that turn
A message sent while Claude is answering joins the running turn. Rewinding to it
forks Claude's conversation at that message, so the chat now drops the rest of
that turn too instead of keeping replies Claude no longer has.
A rewound chat whose kept conversation lives only in another Claude account now
gets the same "history is in another account" refusal as a resume, instead of
Claude's raw start failure on every launch.
* fix(native-chat): rewind only to messages that start a turn
A message sent while the agent is still answering joins that turn. Claude's CLI
holds it until a break and records it after the reply; Orca's chat records it
when it was sent. Rewinding to it therefore cut the chat and Claude's
conversation at different places: the previous fix dropped reply text Claude
still remembered, and the version before kept the message itself. For Codex the
cut started at the turn's own first message, removing a message the user did
not pick.
Rewind now returns only to messages that opened their own turn, the common
pattern: the chat stops offering it on a message sent mid-reply, and the host
refuses one, so a mid-reply message and its turn are always kept or dropped
together. The Claude cut goes back to keeping a kept turn's late rows.
* fix(native-chat): rewind targets follow the turn record's opener; saved rewinds still complete
A message opened its turn when the turn record names it, not only when it sits
at thread level: restored history scopes its openers to their turns, so those
chats lost Rewind.
The opener check now runs only when a new rewind is admitted. Recovery cuts
wherever an already saved rewind points, so one saved by an earlier build
still completes instead of blocking the chat.
* fix(native-chat): recovery's read-ahead reads turns only from a Codex rewind cut
* wip(layout): applyDefaultTabs command in the layout model (dark)
* wip(layout): applyDefaultTabs takes the window's proposed ids; main refuses a taken one
* wip(layout): main closes or keeps an exited split pane by the window's rule (dark)
* wip(layout): a create or split sent again after its close is refused (dark)
* wip: typed test doubles
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main writes a worktree's default tabs in one command and starts them, window or none (dark; twin e)
* wip(layout): main provisions setup and default tabs even when the agent fails to start; no window takes over (dark)
* wip(layout): pane start status in frames; a failed start keeps its pane; a terminal close stands after a failed write (dark)
* wip: lint fixes (max-lines, test throws)
* test(layout): a terminal close stands after a real failed profile write; switch off still refuses
* fix(layout): checked types in the close-failure rule and its tests
* fix(layout): a failed start keeps its pane whatever failed; default tabs start once; a tab's last pane keeps its card (dark)
* test(layout): the agent's kept pane counts as the workspace's terminal; main starts it whatever failed
* fix(layout): the switch-off create path gains no await
* fix(layout): a failed start never writes back a pane the user closed (dark)
* fix(layout): a failed start's pane goes through main's create rules, so a closed tab stays closed (dark)
* test(layout): D1a's switch-on close tests assert the intent main receives (both builds)
* test(layout): the close fixture answers main's command without naming the method
* fix(layout): a re-created pane binds as designed; the id-only ratchet reads a shared import (dark)
* fix(layout): a pane's start status may sit on a pane still bound to its old terminal (dark)
* fix(layout): a pane's start status ends when its pane binds again by any path (dark)
* test(layout): same-id restore ends a status; a replayed split brings back a closed pane by design
* fix(terminal): keep shipped exit and default-tab behaviour with the layout switch on
An exited pane closes through the legacy retirement unless the keep rule holds; the
default tab's title and color still reach the window and the phone's live tabs.
* fix(terminal): one id guard for every pane write; keep panes on an unvouched -1
Main's taken and closed-id guard now runs where every pane write goes, so a runtime create
replayed after its tab closed is refused before it spawns. An exit code nothing vouches for
keeps the pane, as the window does; a kept pane sends the phone no retirement. Converts the
switch-on tests that asserted the deleted window hand-off and title bridge.
* fix(terminal): keep the closed-tab guard on every pane write, held-id checks on client commands
A paired host starts each pane of a client's tab as a create under the tab's id, so a held tab
id is refused only for a client's own layout command.
* fix(terminal): a provider-reported -1 keeps the pane, as the window does
Only a requested stop or a host-confirmed death closes a pane on a negative exit code. Pins that
the closed-tab guard never fires in the shipped build.
* chore(terminal): satisfy the exhaustive-switch and test typing checks
* test(terminal): give the replay test's live process a cwd
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(ssh): an old SSH host converts while a relay shell named only on its tab still runs
The conversion check counted legacy pane-key aliases as terminals waiting to
reconnect. An alias only routes hook events to a pane whose terminal the lease
check, the bridge or the terminal gate already accounts for, so it no longer
blocks the move.
* Tighten the alias census comment
* Register the tab-only relay spec: typed deployment lookup, relay fence baseline, PR route
---------
Co-authored-by: E2E Test <e2e@test.local>
* The window shows each layout records frame record by record, without reading its session back (dark, layout-owner build only)
A records frame no longer re-serializes the window's whole session and runs the
Loader. Panes' terminal bindings and live titles apply alone, to the tabs the
frame names; any other record builds only its own workspace's slices, with the
view and facts read from the store. Only a workspace the store does not show
from frames yet (startup, a reopened stream, a first visit, one sent whole)
takes the full build.
Tests install an apply check that, after every flush, compares the store with
the full build for the same cache and pre-flush store; the shared layout-apply
transport now sends main's records frames and fails a flush that differs.
* A frame of only tab rows shows without reading the window's session back or running the Loader (test, layout-owner build only)
* The account-restart test's fake binding runtime carries per-pane binding generations, so it passes with the switch on (test only)
* The account-restart test types its store writes, so its five old e2e type errors are gone (test only)
* A workspace build reads the editor selection and groups through the save's own writers, as the full build does (dark, layout-owner build only)
* A tab released from the window's hold shows the binding main wrote meanwhile (test, layout-owner build only)
* The frame-apply check still checks a flush's other workspaces when it builds one in full, and the shared transport keeps a test's own check (test only)
* The frame-apply check is proven to catch a mismatch beside a full build, and the shared transport restores only the check it installed (test only)
* Name the pane tree the window compares by what it holds: the tree without its panes' bindings or live titles (dark, layout-owner build only)
---------
Co-authored-by: E2E Test <e2e@test.local>
Main runs one pass at startup and each window load, with or without a window
(desktop, orca serve, orcad): every live local terminal no pane names is bound
to the pane its record names while main still holds it free, else shown in a new
tab bound to it. The window no longer asks main to adopt terminals; it only reads
that main holds them. The window's layout.apply loses its adopt field.
Main also derives activeWorktreeIdsOnShutdown, remoteSessionIdsByTabId and
activeConnectionIdsAtShutdown from its own layout, terminal records and SSH
connections, replacing the window's copies in every save and the quit checkpoint.
Co-authored-by: E2E Test <e2e@test.local>
Behind ORCA_LAYOUT_OWNER_IS_MAIN, a CLI or phone file open is applied by main as an
openEditorTab layout command, so a host with no window can open files. The window
reports each workspace's active group as an optional view fact on syncWindowGraph,
and shows main's tab once the frame that draws it arrives. Editor language
detection moves to shared so main can name a file's language.
Co-authored-by: E2E Test <e2e@test.local>
* fix(renderer): a twin repo id refuses instead of following the Active Server focus, and a listing for a repo with no row is this app's
findRepoOwner no longer lets focus pick between same-id repo rows: runtimeTargetForRepoOwner refuses a
twin, as resolveOwner does, and getRuntimeEnvironmentIdForRepo names no server for one. repoHostId
stamps and routes a repo with no row as this app (or a web client's only server), never the focused
server.
* fix(renderer): a fork names its source's host, and GitHub routing keeps its twin rule until it can refuse
* fix(renderer): a fork captures its source's server transport, and a GitHub edit on a twin refuses instead of using this computer
* fix(automations): a new-per-run workspace is created on the run's captured host
* fix(automations): pass only a captured run host, so a twin without one refuses
* test: stamp the server repo row through a store helper, prune the focus-read baseline
build-orcad-template.mjs takes --dark-test-template to let ORCA_LAYOUT_OWNER=main
through for test servers; the template and out/orcad carry the switch marker, and
packaging refuses a marked template. The e2e template steps build a switch-on server
when the workflow runs with layout_owner=main.
Co-authored-by: E2E Test <e2e@test.local>
* test: shared switch-on fixtures so 71 layout-owner test files run with the switch on
- layoutApplyTestTransport answers layout.apply (accept, apply to main's model with its frame, pending, refuse)
- main's loaded layout as snapshot frames, shared from the frame-sync test
- tab-layout maps for the editor store harnesses; missing store methods in the main harnesses
- workspaceRepos/registerWorkspaceRepos so main's load keeps seeded layouts
- ORCA_LAYOUT_OWNER=main runs vitest with the switch on
- the 4 rollback-build tests stay switch-off, labelled
* test: keep switch-off assertions exact, settle only after layout commands, reset the transport per test
* test: parse the transport's command without Reflect.get; name the staged-session type
* test: pin the test transport in the layout.apply census; route the autosave fixture as unit support
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(renderer): no route resolver reads the Active Server focus, so a local row never routes to the focused server
The worktree route's single-focused-runtime branch, the folder owner's focus fallback and the
worktree host default's focus fallback are gone. The client stamps every server row on arrival, so
an unstamped row is this machine's: terminal spawn and teardown, browser tabs, quick-open, chat and
path links now agree on that. resolveOwnerWithLegacyFallback folds into resolveOwner, and the
focus-free write route becomes the one operation route. Setup terminals keep their explicit
focused-runtime choice.
* fix(renderer): setup terminals keep their focused-runtime host, unstamped repos stay local for the composer, and fixtures stamp server rows
* chore: merge duplicate route imports, drop new fixture casts, prune focus-read baseline
* fix(orcad): ship node-pty's winpty backend so Windows 1809 hosts can open terminals
node-pty selects winpty below Windows build 18309, but the Windows server
slot carried only the ConPTY addon, so the candidate's PTY self-test failed
on Windows 10 1809 / Server 2019 and the host was recorded as unsupported.
Reported by @lksr202 in #30906.
* test(orcad): smoke the winpty backend in the Windows prebuild slot
* fix(ssh): say the host dropped this desktop's pairing, not that another Orca holds the port
* fix(ssh): pair again over SSH when our own managed server refuses this desktop's token
* fix(ssh): key re-pairs by the refused revision, cap attempts, retry on explicit connect
* fix(ssh): keep a newer pairing stored mid-repair, count only completed reads, and offer Connect, not an access link, for managed servers
* fix(ssh): judge a re-pair only against the pairing still stored; Check connection retries a managed server's pairing
* fix(ssh): a pane whose managed server refuses this desktop says why and offers Reconnect, instead of spinning
* test(e2e): live failure path for a refused managed pairing
* fix(ssh): a Connect that re-paired answers from the new pairing, not the retired one
* fix(ssh): only an explicit Connect lifts a managed re-pair's hold and cap; say SSH access can pair again
Startup restore reaches ssh:connect with automatic: true and went through the same
ensureTunnel that cleared the hold, so repeated automatic reconnects could bypass the
three-attempt cap. The reset now lives in connectTarget's explicit branch.
The Shared Server Access footnote now says revoking there doesn't stop someone with
SSH access to this computer from pairing again.
* ci(e2e): route the managed pairing-repair spec with the managed-host remote sources
* fix(ssh): the repair gate decides whether a managed tunnel builds, not the build
A refused shared-control open rebuilt the tunnel (SSH execs, forward, handshake, wake)
before the held failure or the attempt cap was consulted, so reopens on a blocked
server rebuilt without limit. buildWithPairingRepair now asks the gate first: a failure
stands for the refused pairing revision for 15 s, or through the window once the cap is
spent, and every caller gets it without reaching the host.
- A connect during an in-flight repair keeps its reset (per-environment reset epoch).
- An unpaired answer fails at once; it is not a moved port, so no re-read or wake.
- A refusal that only rebuilt the tunnel no longer reports "pairing changed".
- WSL and cloud VMs no longer say Orca tried to pair over SSH, and Check connection
only re-pairs servers managed over SSH (runtimePairingRepairRoute).
- Translate the new strings; route the pane-banner files to the managed-SSH e2e lane.
* fix(ssh): fence a status re-pair follow-up to the same server, and hold a refused repair under its own revision
- getRuntimeEnvironmentStatus followed any pairing change mid-probe, past a caller's
expectedEnvironmentPairingRevision fence. It now follows only for an unpinned caller and only
a new pairing of the same SSH-managed server under its pinned host key.
- recordRefusedAfterRepair stood under whatever revision was stored now, so a newer pairing
stored mid-refusal was blocked untried; it stands under the repaired revision.
- The WSL restart test expects the refused-pairing classification main's WSL path now reaches.
* fix(ssh): a re-pair hands its callers the new credentials, and a failed one judges only the pairing it tried
- ensureOrcadManagedTunnel returns the environment as stored once the tunnel stands. The
terminal census and a deploy's already-current result use it, so after a re-pair they
no longer authenticate with the refused token, which read as an unverifiable census and
refused restarts, updates and stops.
- The shared-control refusal handler blocks the status owner only while the failure stands
for the pairing stored now (managedOrcadPairingFailureStands). A newer pairing stored
meanwhile keeps its owner and retries.
* fix(ssh): migrations commit with the pairing the tunnel stands on, and a Connect mid-handshake stays reset
- Conversion binds the environment ensureOrcadManagedTunnel returns, so the destination, the
server project reads and the result use the re-paired credentials (the migration mark is kept).
- A delta move gets its destination only from connectDestination, which opens the tunnel and
builds the destination from what it returns; a pre-ensure destination can no longer exist. A
tunnel that cannot open keeps the delta journaled and the host marked for the next move.
- buildWithPairingRepair takes the reset epoch when it starts, so a refusal after the re-pair
cannot reinstate a failure that a Connect pressed during the handshake cleared.
* refactor(ssh): one module owns a shared control socket's environment policy, keeping request-connections under its line limit
* feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit
* fix(orcad-migration): refuse clashing drafts from the rename's own collection; stricter live spec
* fix(orcad-migration): stamp renamed drafts with the server, rename at startup after a cut-off commit, keep draft refusals
* fix(orcad-migration): retain only after a startup rename is durable; startup survives an unreadable journal
* fix(orcad-migration): a cut-off commit is renamed and retained at startup, so the rename runs once
* fix(orcad-migration): one retain per migration at a time; no rollback that could drop newer edits
* test(e2e): check the visible draft in whichever editor a large file opens in
* fix(orcad-migration): startup retains only migrations of hosts this profile owns
* ci(e2e): route the editor-drafts migration spec to the sources it covers
* fix(provider-process): kill a provider group its SIGKILLed supervisor leaves behind
* fix(provider-process): re-read a provider start in its read second; poll instead of sleeping in tests
* fix(provider-process): keep the report channel through the Codex session adapter; re-prove same-second starts
* fix(provider-process): never kill a leaderless provider group; list guard files in the CLI project
* fix(layout): restored terminals named only on their tab reattach after a restart (dark, layout-owner build only)
In a layout-owner build, a terminal recorded only on its tab (an older session, or an SSH tab
whose relay session was never visited) started a new shell after a restart and orphaned the old
one, and an unvisited SSH tab lost its relay session id on the next save.
- Main's Loader binds a one-pane tab's own terminal to its unbound pane in a workspace that was
live at shutdown, as the window's reconnect does, so main's frames and saves hold it.
- The window's reconnect writes the restored row terminal again: ptyId is a field the frame merge
leaves to the window, so main's frames never set it on a restored row.
* fix(layout): main binds a restored tab's own terminal once at load, not on every Loader run (dark, layout-owner build only)
The Loader re-derived the binding on each load from fields the window rewrites live, so an exit
main recorded could be undone. A switch-gated Store load repair now binds a one-pane tab's own
terminal to its unbound pane, as the window's reconnect does, and main's saves keep it. It skips
a terminal another mounted pane or a tab-bar tab holds, and a paired server's partition.
* fix(layout): an SSH workspace's restored tab binds only its relay session id; repair runs after the load purge (dark, layout-owner build only)
* test(layout): the reconnect gate test expects the row ptyId write; cover the repair's purge order and SSH repo predicate
---------
Co-authored-by: E2E Test <e2e@test.local>
* fix(runtime): a failed orphan adoption write rolls back instead of keeping the adopted tabs
The adoption rollback ran outside the store's durable transaction, so the store
rebased it like a stale renderer write below the topology fence and kept the
adopted tabs, layouts and incarnations; the next save then persisted them.
Run the adoption through runDurableMutation, whose rollback phase skips that
rebase, and refuse if a queued write changed the session it was checked against.
Co-authored-by: Claude <noreply@anthropic.com>
* fix(runtime): an adoption into an unwritten host partition is not refused as a conflict
Co-authored-by: Claude <noreply@anthropic.com>
* test: run the orphan adoption rollback test in the SQLite runtime project
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(runtime): check and write an orphan adoption inside the store transaction
A snapshot comparison could not see a queued binding write that edits the
session in place. Plan the adoption against the session the transaction
reads instead, so queued writes are validated against rather than guessed at.
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
#29150 removed one layout write from tabs-group-actions.ts without pruning
its baseline entry, so every PR's static analysis fails with 'baseline still
allows 13'.
* fix(diff-comments): a review note on a twin worktree saves to the open copy and names its host
* fix(diff-comments): rendered notes and queued saves follow the open twin
* fix(diff-comments): one host-qualified copy per change; async delivery finds its copy by note id
* fix(diff-comments): a copy is its row's own host; unstamped rows keep the owner their repo names
* fix(diff-comments): the open host picks a twin by the owner index and never falls back to another host's copy
* fix(diff-comments): a worktree's saves stay in one queue when its row gains a host stamp
* fix(diff-comments): one queue and floor per worktree id; paired servers stay separate copies
* refactor(diff-comments): keep main's queue and floors; scope rows only when two hosts publish the id
* fix(diff-comments): per-twin rollback floors that follow the mutation chain; write module split out
* fix(diff-comments): a queued save finds its copy by the list its chain produced
* fix(diff-comments): name the row's own host on every save, and treat a refused save as failed
* fix(diff-comments): a queued save keeps its rows' identity even before a twin appears
* fix(diff-comments): a queued save follows its row when a listing stamps it with the write's host
* fix(diff-comments): refuse a write when the open host's copy isn't listed and another host's is
* fix(diff-comments): a lone unstamped row's queued save survives a stamping refresh
* fix(renderer): host writes route only through the strict owner, and twin projections stay on their host (review-5 #2#9#11)
A selected ssh: host that two paired servers proxy now resolves ambiguous at the one
selected-host resolver, so no caller can fall back to this app's own SSH connection.
File Explorer and SSH mutation expectations derive the full {endpoint, place} pair from
resolveOwner, so a server folder's nested SSH target stays the write's host. A local
lineage write projects onto its own host's rows only. The id-only store-write ratchet
now also covers persistence repo-row lookups by id alone (review-5 #10's shape).
* fix(ratchet): the id-only lookup scan also sees optional chaining and destructured ids
* fix(renderer): an unstamped folder keeps reading its host from its repos; editor focus fallback left for a follow-up
* docs(renderer): the File Explorer owner comment names its folder fallback