Commit Graph
55 Commits
Author SHA1 Message Date
OrcaWinandOrcaWin 3286f3b259 [comment audit] Clean up IPC, host lifecycle and native-chat comments (#32966)
* [comment audit] Tighten watcher lifetime and file-write constraints [skip ci]

* [comment audit] Clarify IPC routing and notification comments [skip ci]

* [comment audit] Shorten watcher, preflight and PTY comments [skip ci]

* [comment audit] Clarify PTY delivery and restore ownership [skip ci]

* [comment audit] Tighten PTY environment and flow-control rationale [skip ci]

* [comment audit] Clarify IPC spawn and stable-pane guarantees [skip ci]

* [comment audit] Tighten PTY provider and filesystem ownership rationale [skip ci]

* [comment audit] Clarify clone ownership and remote runtime recovery [skip ci]

* [comment audit] Condense upload, settings and SSH lifecycle comments [skip ci]

* [comment audit] Shorten shutdown, preview and worktree watcher rationale [skip ci]

* [comment audit] Clarify worktree identity, remote ownership and copy limits [skip ci]

* [comment audit] Shorten worktree catalog, removal and WSL routing rationale [skip ci]

* [comment audit] Tighten Linear credential, pagination and export comments [skip ci]

* [comment audit] Condense managed-server activation, snapshot and launch rationale [skip ci]

* [comment audit] Trim runtime installation and managed-server lifecycle comments [skip ci]

* [comment audit] Simplify managed tunnel, memory and menu comments [skip ci]

* [comment audit] Condense integration, model catalog and attachment rationale [skip ci]

* [comment audit] Shorten journal persistence, replay and queue rationale [skip ci]

* [comment audit] Condense journal ordering and timeline lifecycle comments [skip ci]

* [comment audit] Trim timeline and subscriber delivery explanations [skip ci]

* [comment audit] Shorten Stop, child-exit and recovery contracts [skip ci]

* [comment audit] Simplify handover and host recovery comments [skip ci]

* [comment audit] Trim queue delivery and provider startup commentary [skip ci]

* [comment audit] Clarify restart and rewind safety comments [skip ci]

* [comment audit] Trim status, Stop and completion commentary [skip ci]

* [comment audit] Simplify transcript lookup and visual containment comments [skip ci]

* [comment audit] Trim transcript reader, watcher and WSL transport commentary [skip ci]

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 19:07:32 -07:00
NeilandJesse Sanford 7cafdb072b feat(ssh): Recover server for a managed host refused on a stale lock, and surface that refusal in seconds (#32951)
* feat(orcad): run --reclaim-stale-lock on a managed host's active slot

The desktop can now dry-run and confirm the stale instance-lock recovery
on a Linux managed host, serialized with launches, then start the server
again and reconnect the way the restart action does.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* feat(settings): offer Recover server for a managed host refused on a stale lock

When a managed orcad start names --reclaim-stale-lock, the SSH host row
offers Recover server. The dialog dry-runs the check, shows the host's
evidence, and clears the lock only after the user confirms no other
machine sharing the home runs Orca.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* feat(mobile): point a host refused on a stale lock to the desktop's Recover server

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): mount the Recover server dialog per opening

A fresh mount starts each opening from a new check instead of resetting
state in an effect.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): end the readiness wait once the host proves the launch exited

A managed orcad refused at start (such as on a possibly stale instance
lock) left the client waiting out the full readiness deadline. Each
host-side wait now also checks the launch's PID file with kill -0 and
ends early on a proven exit, so the caller reports the same pending
readiness, with the orcad.log tail, within a poll. No PID, a denied
probe or a reused PID keeps the old deadline.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): refuse a managed slot without --reclaim-stale-lock before running it

An older orcad ignores the flag and starts serving in the foreground. The
same remote exec now checks that the slot's server bundle names the flag
and answers unsupported otherwise, without invoking orcad.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): show the manual stale-lock command as literal mono text

The rendered dialog ran the command into the sentence, so it was hard to
read or copy. It now sits in its own mono block, like Details.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): offer only a manual delete when the host can't reclaim, and format the lock's age

An unsupported host was told to run the very command it lacks. Main now
names the lock path for that case, and the dialog shows only a copyable
rm of it with the shared-home caution. The lock's age uses the UI's
relative-time formatter instead of a hand-rolled minutes count.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): pin a confirmed stale-lock reclaim to the record the user approved

--yes reclaimed whatever record held the lock at that moment, so a lock a
same-named machine wrote while the dialog sat open could be cleared.
orcad now takes --expect-nonce and refuses unless the lock still holds
it; the dry run reports the nonce, the desktop sends it back on confirm,
and the managed slot gate requires a build that knows the pin. A
reclaimed result carries the evidence it acted on.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): keep the Recover server dialog open through its own reclaim

The dialog's mount was gated on the status that offered it, and the
reclaim clears that note before its answer arrives, so the result was
never shown and the card kept the dialog marked open. Only the triggers
are gated now; an open dialog keeps the server it opened for until the
user closes it.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): put the Recover server confirmation on the security surface

It asks permission to run a remote command that clears an ownership lock
and starts server code, which STYLEGUIDE puts on that surface.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): trust a gone launch PID only on the machine that launched it

The PID file sits in a home other login nodes may share, so a wait exec
that lands on another node read a starting server as exited. Both launch
paths now record the launching machine's boot id and node name beside
the PID, and the readiness wait ends early only when its own exec reads
the same record. No record, a host without a boot id, or another machine
keeps the deadline. The PID path is named once in the launch command so
it stays within its sent-byte ceiling.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): keep a slot's unreadable stale-lock answer from offering a manual delete

A slot that passed the gate but gave no JSON, or a status this client
does not know, read as unsupported and offered rm of the lock. It now
reads as no-answer, which points to the slot's own reclaim command, and
unsupported is left to a slot without the command, a non-Linux host or
no active version.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* style(settings): use the text-2xs step for the stale-lock command block

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(mobile): offer the desktop-recovery hint on the same refusal the desktop does

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* test(orcad): name the extra fields a fake stale-lock answer carries

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(settings): end the Recover server flow once the lock is cleared, and keep an unpinned reclaim manual

After a reclaim whose restart failed, the dialog kept the old evidence,
the enabled confirmation and manual recovery for a lock that was gone.
It now shows only that the lock was cleared and why the server did not
start, with Close. A slot that has the reclaim but not the nonce pin is
now told apart from one without the reclaim, and is pointed to its own
reclaim command rather than rm.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): keep a stale-lock reclaim's result when the restart after it throws

A wake or tunnel rebuild that threw after the lock was cleared discarded
the reclaimed result, so the caller skipped its reconnect and note
clearing and the dialog kept a confirmation for a lock that was gone.
The restart's failure now becomes the result's notRestarted reason.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* fix(orcad): report a reclaimed server as started when only its forward failed to follow

A tunnel rebuild that failed after the wake started the server was
reported as the server not starting. The rebuild now goes through
followLaunchedServerPort, as other relaunches do, which warns and leaves
the forward to the next connect. Also drops a caller test that only
covered behaviour the fix did not change.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

* test(mobile): re-record the execution-target goldens for the recovery hint field

The SSH gate's projected state gained `needsDesktopServerRecovery`, which
is false for every recorded reply (no recorded host sends a managed
server refusal). Re-recorded with `pnpm --dir mobile rpc:record` for the
two pilot recordings and the four execution-target reply partitions; the
only change is that field, plus the value pool reordering around it.

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>

---------

Co-authored-by: Jesse Sanford <108698+jessesanford@users.noreply.github.com>
2026-10-23 17:57:22 -07:00
Neil 6ed9382c05 Allow SSH migration recovery without stopping preserved relay terminals (#32914)
* fix(ssh): preserve running relay panes during migration recovery

* fix(ssh): wire pane eligibility through existing relay seams
2026-10-23 13:54:17 -07:00
NeilandFrank-zhu2000 92c8f3cbdc fix(ssh): stop floating terminal metadata from blocking SSH access links (#31963)
* fix(ssh): stop local-only workspace metadata from blocking SSH access links

Unstamped floating and setup terminal metadata rows were projected onto every
execution host, so the dependent-state census refused SSH access links and
empty-host claims for every SSH target with orcad_migration_dependent_state.

- Local-only workspace ids project only onto the local host, are stamped local
  on load, and their metadata writes always land on the local host.
- The census counts an unstamped legacy row only when its repo is not
  registered solely on other hosts.
- The SSH access error now names the blocking state.

Builds on the approach in #31862 by Frank-zhu2000.

Fixes #31498

Co-authored-by: Frank-zhu2000 <Frank-zhu2000@users.noreply.github.com>

* fix(ssh): rehome remote-stamped local-only metadata, skip orphan rows, and word the SSH access error for a link

* fix(persistence): keep a local or unstamped floating row over a remote copy when rehoming

* fix(ssh): make the SSH access blocker copy switch exhaustive

---------

Co-authored-by: Frank-zhu2000 <Frank-zhu2000@users.noreply.github.com>
2026-10-22 03:39:54 -07:00
Neil d5d9170475 refactor(managed-server): stop exporting declarations used only inside their own module (#31888) 2026-10-22 01:01:01 -07:00
Neil 1769476014 fix(wsl-host): pair again from the distro when a WSL host refuses the enrolled token (#31745)
* fix(wsl-host): pair again from the distro when a WSL host refuses the enrolled token

A WSL host that lost its device registry, or revoked this desktop, refused the
enrolled token forever, and an update over it read as deferred. The connect now
re-pairs through the managed repair gate SSH uses (one repair at a time, a
capped number per window, only an offer under the pinned host key), reading the
host's published offer from the distro. The incumbent's terminal count uses the
same enrolled-or-live pairing the connect does.

* fix(wsl-host): retire a bridge whose pairing was refused, and let a user's Connect retry a repair

A refused or repaired-then-refused pairing on a reused bridge left its stale
token cached, and the failure status then saved it over the repair. That bridge
now closes first. A user-initiated Connect clears a standing repair failure or a
spent cap, as an explicit SSH connect does.

* fix(wsl-host): retire a refused bridge without cancelling a queued Update or Force

* fix(wsl-host): repair only a stored pairing, dispose the repair's transport directly, and log refusals neutrally

A host still being added has no stored record for the gate to re-pair, so its
live bridge is reused as before. The WSL repair deps are typed with the WSL
transport, and the shared refusal log no longer says SSH for WSL.
2026-10-21 23:15:59 -07:00
Neil eb9d2f9a06 fix(security): update Node runtimes for October security releases (#31687)
* fix(security): update Node runtimes for October security releases

* fix(ci): compare normalized web file inventory without ordering artifacts
2026-10-21 17:06:28 -07:00
Neil a57e027cb3 fix(orcad): one update decision for SSH and WSL, with connect, Update and Force intents (#31682)
* fix(wsl-host): apply the SSH update rules to a WSL connect

A WSL connect deployed this app's orcad whenever the active build differed,
so a distro another, newer Orca had activated ended in an unconnectable
deferral, and a build the host had rolled back from was re-activated on
every connect. It now runs the same planManagedOrcadAutoUpdate decision SSH
uses and serves the active build when that plan skips.

* fix(wsl-host): serve the skipped-update incumbent only if it is still active

* fix(orcad): one update decision for SSH and WSL, applied again under the fence

The connect's update plan moves to its own module, and the fenced admission
every first deploy and WSL connect uses now applies it, so a transient
readiness failure or a race cannot reactivate a rolled-back build.

* refactor(wsl-host): adopt the serving build through the SSH first-deploy adoption

* fix(wsl-host): do not retry a candidate rejected earlier this launch

A rejected candidate is restored without recording rolledBackFrom, so WSL
retried it on every connect. SSH's launch-scoped failure memory moves to a
shared helper keyed per host, and WSL feeds it to the shared update decision.
A null adoption now re-reads the record before the deploy, as SSH does.

* fix(wsl-host): keep Force offered when a connect skips this app's build

The WSL connect's update admission moves to its own module, which returns a
forceable hint when it serves the incumbent over a rolled-back or failed build.

* fix(orcad): connect, Update and Force are the three update intents on SSH and WSL

Only a connect applies the skips; an explicit Update retries a skipped or failed
build (clearing the failure memory) behind the terminal census; only Force passes
the census. WSL's Update button now sends the update intent instead of a plain
connect. A connect that records a rejection serves the restored incumbent at once.

* fix(orcad): an explicit Update keeps the newer-host skip on SSH and WSL

Update lifts only a host's own skips (rolled back, failed before); Force lifts
all. SSH's explicit update now applies the same fenced admission, so stable
cannot replace a host a newer Orca activated without Force. A rejection retries
adoption only once the incumbent is back and no journal awaits recovery.

* fix(wsl-host): keep Update and Force offered on a deferred cold connect

A connect the host deferred with nothing serving now publishes its deferral, so
a host stopped after a rollback can still be retried from the UI. SSH's explicit
Update forgets a remembered failure only once it lands, and a restore that could
not be read keeps the rejection as reported.

* fix(wsl-host): offer only Force over a host a newer Orca runs, and keep failure memory until an Update lands

A host-newer refusal gets its own deferral code so the WSL row hides Update,
which can never pass it. An explicit Update no longer clears the launch failure
memory up front; only a landed deploy does, as on SSH.

* refactor(wsl-host): merge the intent type into the existing shared imports
2026-10-21 16:26:35 -07:00
Neil fba3af0b48 docs(ssh): describe the orcad connect path without the removed relay (#31651)
Comments and one log line still said a host falls back to the relay, keeps
the pinned-relay ladder, or shares code with the relay's spawn and install
paths. No relay client exists; an unserved host fails its connect with a
reason, and the legacy bridge only keeps old relay terminals alive.
2026-10-21 13:55:25 -07:00
Neil e7d77e863c refactor(orcad): name the remote install helpers for what they install (#31647)
The SFTP/exec-stdin transfer helpers, their options type and the install
timeout still carried relay names from before orcad; they install the orcad
bundle and the Node runtime. The timeout's derivation from the relay's
native-deps budget is replaced by its value and the reason for it.
2026-10-21 13:54:26 -07:00
Neil f9aeb34e00 refactor(ssh): import SSH and orcad names from the modules that define them (#31642)
Drops seven pass-through re-exports in the SSH, managed-server, orcad and
runtime-environment modules; each importer now names the defining module.
2026-10-21 13:28:15 -07:00
Neil 7d43ed9024 refactor(orcad): share one unverifiable terminal census (#31633)
Five modules each declared the same all-null census for a daemon that could
not answer; they now use one frozen constant from the shared census module.
2026-10-21 13:16:19 -07:00
Neil 67886127dc refactor(ssh): delete SSH and orcad helpers nothing calls (#31629)
Removes the relay ladder's answered-failure predicate, the unused test-probe
joins, two dead renderer broadcast helpers, the bound-port reader the tunnel
no longer uses, the WSL terminal-verdict mapper the supervisor never called,
and an unused type.
2026-10-21 13:00:00 -07:00
Neil dafed6b345 fix(orcad): hold terminal admission through a server stop; fence rollback and restart (#31418)
* fix(orcad): hold terminal admission from a stop's start until exit; fence rollback and restart counts

Once orcad begins any stop (signal, managed stop request, idle exit), its terminal
admission stays closed until the process exits, past an update census's 60s fence
or a deferring desktop's reopen. Rollback and restart now take their count through
the same fenced recount the update uses, reopen admission when they leave the
server serving, and stop nothing once that count may have outlived its fence.

* fix(orcad): check the rollback's stop deadline after its transaction write

* fix(orcad): rollback and restart recount under the host lock; a reopen never wakes a server

Only the lock holder closes or reopens a server's terminal admission, so a
rollback or restart that loses the lock can no longer clear another update's
fence. A reopen skips the serving check, so it never starts or recovers a
stopped server, and a stop that begins before the runtime binds still holds.

* fix(orcad): reopen admission when a rollback's stop was withdrawn

* fix(orcad): keep admission closed after a stop that may still be on its way

* refactor(orcad): route update, rollback and restart through one fenced stop

runFencedStop is the single count, close, stop, reopen-on-abort sequence. The
update's activation, the rollback and the restart each supply only their own
refusal, journal step and stop; the reopen and deadline rules live in one place.
2026-10-21 11:57:39 -07:00
NeilandClaude 5dd5294294 perf(orcad): keep the server bundle ASCII so V8 stores it at half the size (#31495)
* perf(orcad): keep the server bundle ASCII so V8 stores it one byte per character

129 non-ASCII characters in regex literals (and three in a String.raw plugin
template) made V8 hold the whole ~11 MB orcad-server.js source as a two-byte
string. Write them as \uXXXX escapes and fail the build if any return.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(orcad): keep license comments ASCII and name the module the guard trips on

esbuild gathers legal comments at the end of the bundle; escape that comment so
a dependency's license text cannot fail the ASCII guard, while licenses still
ship with the code. The guard now reports the offset and, from an unminified
rebuild, the module the character came from. A dark-switch build is covered.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-21 04:06:49 -07:00
Neil d1ed73ba9b fix(orcad): refuse new terminals while an update stops the server (R32) (#29834)
* fix(orcad): refuse new terminals between an update's last census and the stop

The last census before an SSH or WSL update stops the server now closes the
server's terminal admission first. Every spawn lease (acquireWorktreeTerminalSpawn,
which both spawn paths hold across the provider spawn) passes the fence, the
census waits for leases taken before the close, and an update that leaves the
incumbent serving reopens it. A reattach to a session the daemon still runs is
still admitted.

* fix(orcad): fence-exempt reattaches spawn attach-only; reopen admission when no stop was sent

* fix(orcad): stop nothing once the recount's admission fence may have lapsed
2026-10-20 21:23:41 -07:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
JinjingandNeil 0a5a7b1446 fix(orcad): run the remote daemon at the login umask so worktrees it creates are not owner-only (#31135)
* fix(orcad): run the remote daemon at the login umask so worktrees it creates are not owner-only (#31125)

* fix(orcad): keep refreshed Gemini OAuth tokens owner-only now that orcad runs at the login umask

The Gemini usage fetcher rewrote ~/.gemini/oauth_creds.json through a temp file and rename with
no mode, so the replacement took the process umask. Under the old inherited umask 077 that
happened to be 0600 on SSH hosts; at the login umask it would be 0644. Write it through
writeCredentialFileAtomic (0600 staging file, owner-only ACL on Windows).

The hostile-host matrix now checks that the deployed orcad runs at the SSH login's umask and that
its readiness file stays 0600.

* fix(orcad): keep rewritten agent configs at their mode and create the launch PID file owner-only

writeHooksJson now keeps an existing config's mode unless a caller opts out. Rewriting
~/.claude/settings.json (which can hold API keys) and the other agent configs through a temp file
used to take the process umask; inside orcad that was 077, and at the login umask it would widen an
owner-only file to 0644.

The launch runs `umask 077` in the foreground shell rather than inside the backgrounded nohup
list, so the PID file the foreground writes is created 0600 instead of relying on a racing chmod.

The Gemini credential test now pins umask 022 rather than inheriting the runner's.

* fix(agent-hooks): do not create a read-only temp file for a read-only Windows config

A preserved Windows mode can be read-only, which would leave an undeletable temp file behind when
the rename fails. POSIX keeps the preserved mode.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-20 13:39:32 -07:00
Neil 49a2203b0e Remote Orca Servers: one list instead of six sections; SSH Hosts: bulk remove that stays removed (#31082)
* feat(settings): one-list Remote Orca Servers page and bulk-manageable SSH Hosts

Remote Orca Servers: replace the three mode cards, Advanced fold, duplicate
managed/SSH-access lists and troubleshooting accordion with a single list
(one row per server: status, one action, overflow menu), an always-visible
'New projects open on' row, and Share / Cloud VM cards that open dialogs.

SSH Hosts: group by source, filter, checkbox + shift-range selection, one
confirm for bulk remove (skipping hosts with a managed server or
workspaces), bulk Test. 'Sync ~/.ssh/config' no longer clears removal
tombstones; 'Restore removed hosts (n)' is a separate confirmed action.

* fix(settings): address review findings on servers and SSH hosts redesign

- SSH filter stays clearable when the list shrinks under the threshold
- Cmd/Ctrl-A only selects hosts when pressed in the list itself
- group legacy no-source hosts with main's import predicate (now shared)
- capture the shift-range anchor before queuing the selection update
- blank server names de-duplicate instead of colliding
- Share dialog Done refreshes link count and resets name editing
- Install / SSH-route dialogs reload SSH hosts when opened
- default-host row shows the selector when the default is a WSL host
- managed 'Not running' no longer masks connecting/revoked/update states
- servers without a dropped transport can be re-checked

* test(e2e): WSL host spec matches repo.rm ending a removed project's shells

repo.rm now stops the project's terminals on the host (#29964), so the
'orphan left by older builds' setup can no longer be built; use a shell
only the host knows of instead. Resolve the project path so macOS
/private/var matches what the host reports.

* fix(settings): carry 'Orca on <host>' naming and paired-twice warning into the new server list

Ports #31099 and #31100 into the redesigned rows, install dialog and SSH
host dialogs.

* test(e2e): keep the WSL spec's project path as created
2026-10-20 05:22:39 -07:00
NeilandClaude 0ee13f831d fix(ssh): say "Orca on <host>" instead of "managed server" in the UI (#31099)
* fix(ssh): say where Orca runs instead of "managed server" in the UI

Users read "managed server" as hosting run by someone else, so they declined
moving their SSH hosts. Every user-facing string now says "Orca on <host>" or
"this host's Orca server", in all six locales; Settings > "Orca on your SSH
hosts" and the setup line say the code and files stay where they are and only
Orca's list of projects, tabs and terminals moves to the host. CLI command names,
logs and identifiers are unchanged.

Fixes #30880

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): reassure only when something moves; name the host's label in its row

Review follow-ups: the reassurance shows only while Orca sets itself up or
moves a host, not on every reconnect; the converting line no longer repeats it;
the row names the SSH host's current label; the section description and the
Move anyway dialog stop repeating themselves; translate the Restart dialog's
buttons; drop "managed" from the remaining raw causes and the external
automation refusal.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 04:18:08 -07:00
e3ad4acaa3 fix(ssh): a managed tunnel whose forward can't start takes the stdio bridge, and reclaims a port an old ControlMaster holds (#31062)
* fix(ssh): recover a tunnel port a ControlMaster still holds

A forward made through a ControlMaster leaves its local listener on the
master: the `-N` client exits at once and the master keeps the port. No
process of Orca's holds it then, so the orphan sweep finds no argv to
match and a managed tunnel's persisted port stays bound - every later
connect fails with `Local port 127.0.0.1:<port> is not available:
EADDRINUSE`, until the user finds and stops that master by hand.

Ask the master to drop just this forward (`ssh -O cancel -L`) when the
orphan sweep comes up empty, then rebind. OpenSSH refuses a spec no
master registered, so another forward on the port, and a port held by
anything but a master, are left alone - as are the master's own
sessions and its other forwards.

* fix(ssh): read a refused forward cancel off stderr, not the exit code

`ssh -O cancel` exits 0 whether or not a master held the spec, reporting
a refusal only as `master cancel forward request failed` on stderr. So a
spec no master registered read as a release that freed the port: the
recovery logged a cancel it never made and let the caller rebind a port
still held, turning a clear refusal into a second bind failure.

Treat a request failure on stderr as nothing released, so a port held by
anything but a matching master keeps reporting the original bind error.

* docs(ssh): document the port reservation contract and its recovery limits

State on each entry point what it returns and what it will not do: that
reservation only ever recovers a port already in use and only from what
the caller supplies, that the bind reports a busy port through the
error cause, and that a forward on port 0 carries no recovery at all
because the port it lands on was just free.

* fix(ssh): any managed-tunnel forward that fails to start falls back to the stdio bridge

* fix(ssh): skip the master cancel on Windows; note the forward's bridge fallback

* test(ssh): an abandoned forward's late exit uses a real close reason

* fix(ssh): cancel only Orca's forward on the master, and bridge a forward that never listens

Codex: -O cancel also cancels every forward the loaded ssh_config declares, so the
cancel now reads the control socket from ssh -G and sends the request with no config.
Opus: a forward still waiting on its own login never binds; that now takes the bridge.

* fix(ssh): pass the master's ControlPath through -S so a path with spaces stays whole

* fix(ssh): keep ssh's own exit reason when the forward also never listened

* test(ssh): the real sshd fixture waits for its processes before removing its temp dir

---------

Co-authored-by: zzzpyyy <167217540+zzzpyyy@users.noreply.github.com>
Co-authored-by: zhangpengyun <zhangpengyun@zpydeMacBook-Pro-2.local>
2026-10-20 02:26:46 -07:00
Neil e0cf91c62a feat(ssh): one-click Node.js mirror when neither machine can download Node (#31069)
* feat(ssh): offer a Node.js mirror when neither machine can download Node

* fix(ssh): put the mirror action beside Details on the host row

* fix(ssh): offer the mirror only where this computer's setting applies, and word main's error by mirror state

* test(ssh): type the refusal case

* test(ssh): one checked settings fixture for the mirror dialog
2026-10-20 01:55:13 -07:00
Neil ba6599eabb fix(ssh): pair again over SSH when a managed server drops this desktop, instead of reconnecting forever (#30775)
* 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
2026-10-19 22:45:25 -07:00
Neil e278bc9a17 feat(orcad-migration): unsaved drafts stay on this desktop and are renamed to the server on commit (#30894)
* 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
2026-10-19 22:44:43 -07:00
Neil 66d37178da feat(ssh): let a nodejs.org mirror serve the pinned Node on this computer and the host (#30981)
* feat(ssh): let a nodejs.org mirror serve the pinned Node on both machines

* fix(ssh): share one deadline across every host download URL

* fix(ssh): give each host download URL its share of the deadline and cap its size

* refactor(ssh): one tool per host, labelled sources and a portable wget watchdog

* fix(ssh): quote this computer's short download reason in the combined error

* fix(ssh): fetch only validated mirror URLs on the host

* fix(ssh): drop an empty query or fragment from the mirror URL

* fix(ssh): keep a cancel or a local error as it is after the mirror fails
2026-10-19 20:52:44 -07:00
Neil c822123b43 fix(ssh): a forward edit cut short by Disconnect keeps the host's saved port forwards (#30932)
* fix(ssh): a port forward never starts on a connection its teardown already disposed

* fix(ssh): a Disconnect-cancelled edit still resyncs the renderer, and tests model a disposed connection faithfully
2026-10-19 19:57:47 -07:00
Neil b6eee3e981 fix(ssh): download the pinned Node on the host when this computer can't reach it (#30945)
* fix(ssh): let the host download the pinned Node when this computer can't

* fix(ssh): keep both host download tools inside one time budget

* fix(ssh): use timeout for wget only where it runs as expected
2026-10-19 19:56:11 -07:00
Neil bfdf16b253 style: format three files #29042 landed before the format check (#30948)
Its CI ran before #30863 added the check. Formatting pushed wsl-orcad-connection.ts past its line
limit, so its port-fallback listen moves next to the stdio bridge it wraps.
2026-10-19 18:46:58 -07:00
Brennan Benson 65d671ecc9 fix(wsl): keep the desktop pairing across host restarts (#29042)
* fix(wsl): retain the paired desktop identity across host restarts

* refactor(wsl): resolve enrolled pairing in the identity module

* test(wsl): supply the required host readiness state

* fix(wsl): resolve registered pairings through the saved authority
2026-10-19 18:27:48 -07:00
Neil c9e7147f84 fix(managed-server): keep the tunnel manager under its line limit after #30888 and #30891 crossed (#30933)
The build's same-pairing check moves next to managedTunnelAccess, which it already wraps.
2026-10-19 17:56:49 -07:00
NeilandClaude 6f96efb662 fix(orcad-migration): a host fenced on a manifest no request can carry can be cancelled by its id (#30864)
* fix(orcad-migration): a source fenced on a manifest no request can carry can be aborted by its identity

A desktop that fenced a manifest just under the export cap before #30836
could never send its abort: every catalog request carries the whole
manifest and refuses it before sending. The abort now asks the server by
migration id and digest instead (orcad.migration.abortCatalogByReference,
behind a new capability), and releases only on the server's durable
absent answer. A committed server still refuses the release.

Co-authored-by: Claude <noreply@anthropic.com>

* test(orcad-migration): cover the durable abort by reference and its RPC method

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 17:25:47 -07:00
Jinwoo HongandE2E Test e7d6ee645b chore(lint): bring orcad-managed-tunnel-manager.ts back under max-lines (#30921)
Move the 'recorded tunnel still matches this build' check into
orcad-managed-tunnel-active.ts as activeTunnelMatches(). No behaviour change.

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 19:17:28 -04:00
Neil 0a3d203f61 fix(ssh): Disconnect closes the managed server tunnels the SSH session carried (#30891)
* fix(ssh): Disconnect closes the managed server tunnels the SSH session carried

The managed tunnels live in their own manager, so the session teardown closed ordinary
forwards and the connection but left a system-SSH tunnel's ssh -L process authenticated and
listening. The teardown now closes every managed tunnel built or building on the target.

* fix(ssh): a session teardown also closes forwards still starting or awaiting their identity check

* fix(ssh): a connection teardown waits for every forward of it to settle, starts and closes under way included

* fix(ssh): bound a teardown's wait on starts, skip edit rollback after one, and never record a tunnel a teardown overtook

* fix(ssh): an edit a Disconnect cancelled leaves the target's saved forwards alone
2026-10-19 16:00:38 -07:00
Neil 98ee268825 fix(ssh): a managed tunnel follows the host's bound port after another desktop restarts the server (#30888)
The tunnel was rebuilt only when this desktop's own check saw the server start. When another
desktop restarted it onto a new port, the old forward stayed and every check answered
unverifiable. A check that finds the server not answering now reads the port the host's
readiness record names and, when it moved, rebuilds the tunnel there.
2026-10-19 15:39:54 -07:00
Neil 209e468a7c fix(ssh): an SSH server update counts terminals again just before it stops the server (#30874)
* fix(ssh): an SSH server update counts terminals again just before it stops the server

The SSH update passed the terminal census taken before the artifact upload and no recount,
so a terminal opened during the upload was missed and, on a host running terminals inside
orcad, ended by the restart. The recount is now a required deploy option, so every runtime
(SSH, WSL, first deploy) supplies one and the stop step always recounts.

* fix(ssh): a recount that throws reads as unverifiable and defers the update
2026-10-19 15:34:16 -07:00
NeilandClaude 2b07f0f3b6 fix(managed-server): snapshot capture and restore run on BusyBox before 1.30 (#30840)
The state mutation wrapper ran `timeout -s KILL 900 sh -c ...`, which old
BusyBox (`timeout [-t SECS] [-s SIG] PROG`) reads as running a program
named 900. The script never ran, the capture answered nothing and parsed
as failed, so every update stopped the server and rolled back. Probe the
syntax as the room check (#30618) already does.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 12:52:55 -07:00
Neil 43abe329c5 refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once (#30744)
* refactor(orcad): every snapshot state op fits the gateway exec budget in shell; the state lock sends its script once

* test(orcad): a restore that stops part way reports FAILED and reruns from the archive
2026-10-19 07:54:34 -07:00
Neil 5a9e26ef9f fix(ssh): say a noexec home can't run Orca's server, instead of "install an older version" (#30724)
* fix(ssh): name a noexec home instead of calling the host unsupported

* test(ssh): measure the stage exec probe in the host-op byte budget

* fix(ssh): the connect error names a noexec home too

* chore(i18n): add the noexec status line to the English catalog

* fix(ssh): only the shell's exec refusal reads as noexec; other probe failures reach the self-test

* fix(ssh): a stage the probe can't write skips the probe, so a full disk still reads as full

* fix(ssh): read the probe's exit 126 as noexec in any locale
2026-10-19 05:45:39 -07:00
NeilandClaude e72d8f926a fix(ssh): say the host's disk is full when a managed-server install runs out of space (#30679)
* fix(ssh): say the host's disk is full when a managed-server install write fails for lack of space

OpenSSH's sftp-server reports ENOSPC and EDQUOT as the generic SSH_FX_FAILURE, so a deploy onto a
full home failed with the bare detail "Failure" under "Try connecting again". After a failed
install write, probe the host's free space where the write was going; when it cannot hold the
write, fail with an error that names the filesystem, how much is free and how much is needed.
Lost or aborted transfers and Windows hosts keep their original error.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): diagnose only SFTP's generic failure on an exhausted filesystem

Codex review: a partial upload may spend the room the rest needed, so compare against an empty
filesystem, not the bundle size, and keep explicit refusals such as permission denied.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(ssh): reuse formatKb and size an upload the way uploadDirectory walks it

Opus review: the shared formatKb already exists, and following symlinks inside the tree could
recurse; uploadDirectory follows the root and skips links below it.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(ssh): measure the free-space probe in the host-op byte budget

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 04:20:09 -07:00
Neil 4f425a2861 fix(cli): refuse server names --environment cannot tell apart, and say how to pick one of twin projects (#30676)
* fix(cli): refuse a paired server name --environment cannot tell apart, and say how to pick one of twin projects

* fix(cli): point an ambiguous --worktree at its host-qualified identity

* fix(cli): keep the ambiguity listing on the paired server the command chose

* fix(cli): compare saved server names the way --environment does, and route cross-host twin projects to --project --host
2026-10-19 03:12:30 -07:00
NeilandClaude Opus 5.5 912cb06398 fix(managed-server): repair main typecheck after the byte-budget and snapshot-room PRs crossed (#30680)
* fix(managed-server): import newestStateMtimeCommand from its new module

#30635 and #30618 merged in parallel: #30618 moved newestStateMtimeCommand
to orcad-state-newest-mtime.ts while #30635's byte-budget test imported it from
orcad-state-snapshot.ts, breaking typecheck on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(managed-server): measure the snapshot-space probe and record #30618's capture growth

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 02:29:45 -07:00
NeilandClaude 79b7d702be test(managed-server): 6 KB wire-size budget for every POSIX host-op command (#30635)
* test(managed-server): byte budget for every POSIX host-op command

Builds each exported POSIX host-op builder at realistic maximal arguments and
asserts the raw command stays under 6 KB, under the 8,000-byte compression
threshold. Ops already over budget are listed with ceilings that may only
shrink until they move onto the host script (round 4 R4-10).

Co-Authored-By: Claude <noreply@anthropic.com>

* test(managed-server): budget the wrapped, fence-guarded command on Linux and macOS

Measures what a gateway sees on the plain fallback (fence guard + uncompressed
POSIX wrapper), takes the larger of Linux and macOS, launches the SSH way (nohup),
and also discovers the top-level src/main/ssh builders. Exports three builders
that are sent as their own exec request so they can be measured.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(managed-server): count the stdout fence; scan ssh transport subfolders

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 01:55:29 -07:00
NeilandClaude a451f472c3 fix(ssh): make room for the pre-activation snapshot on remote hosts, and say why a capture failed (#30618)
* fix(ssh): make room for the pre-activation snapshot, and say why a capture failed

Every update left its own full state copy behind, and a failed capture its partial archive, so a
small or quota-limited home filled until no update could snapshot. The capture now prunes every
pre-* snapshot except the one the activation record names, removes its own partial archive on
failure, and prints tar's error. Before stopping the running server, the activation checks that
the snapshot fits, so a host without room keeps its server.

Refs #30491

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): the room refusal says what is free apart from what a prune reclaims

It also stops claiming the recorded server is running, and a test pins that the record's
snapshot survives both the room check and the prune.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): measure snapshot room before the last terminal recount, within a deadline

A slow scan after the recount could let a terminal opened meanwhile be stopped. The scan now runs
first, gives up after 12 s per du (UNMEASURED reads as unknown), and a lost probe channel leaves
the room unknown rather than failing the update. The newest-mtime probe moves to its own module
to keep the snapshot module under its line limit.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): pick timeout's syntax for the room probe, and pin its failure paths

BusyBox before 1.30 takes `timeout -t SECS`; plain `timeout 12` disabled the probe there.
Tests cover a timeout on the old-snapshot scan and a lost fence from the probe.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 01:35:18 -07:00
NeilandClaude 36ed236394 fix(persistence): repo writes take the row's identity, never a bare id (#30616)
* fix(persistence): repo writes take the row's identity, never a bare id

updateRepo(target, updates) now takes Pick<Repo,'id'|'connectionId'|'executionHostId'>
(RepoHostTarget) and resolves the host from it, so a caller that holds the row can no
longer drop its host and write a same-id sibling on another host. The id-only callers
(project host setup controller and IPC handler, folder-to-Git upgrade watch, both
clone paths, registration adoption, fork backfill) pass the row they resolved.

Co-Authored-By: Claude <noreply@anthropic.com>

* test: migrate the remaining Store.updateRepo callers to row targets

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 00:33:50 -07:00
21af18af35 fix(ssh): managed servers install through SSH gateways that cut long commands (#30491) (#30549)
* fix(ssh): preserve activation commands through SSH gateways

* fix(ssh): fall back when a host cannot decode a long command, and read claims and snapshot verdicts through gateway noise

On top of the gateway fix (#30503):

- A host without base64 or gzip gets a long command plain: the compressed wrapper
  checks for its decoders before reading anything, exits 125 with a marker, and
  execCommand resends once with compressCommand: false and remembers that host.
  `base64 -D` covers macOS before 13, whose base64 refuses `-d`.
- Snapshot capture, restore, presence and compare answers read the last line that is
  a verdict, so shell noise a stderr-merging gateway appends after it is ignored.
- The machine claim reads the claim line by line, and every failed step now prints
  CLAIM_FAILED <step> <error> with the df line, so "orcad could not claim this host's
  .orca-remote" says what failed and how to fix it instead of exiting 65 with the raw
  command or failing with no reason. A failed write no longer leaves a partial file.

Refs #30491

* test(ssh): size the simulated gateway cut from the command, not a fixed 10 KiB

The snapshot command repeats its temp path ~40 times, so on CI's short /tmp it fell under 10 KiB
and the truncation precondition failed.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: zzzpyyy <167217540+zzzpyyy@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-19 00:03:01 -07:00
NeilandClaude 24e218c101 refactor(shared): move orcad-migration-* into src/shared/orcad-migration/ (#30536)
Pure git mv plus import and path-literal rewrites. Two files #29339 edits stay put:
orcad-migration-dormant-state-validation.ts and orcad-migration-manifest-contract.test.ts.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 21:28:03 -07:00
NeilandClaude 6328a104bd refactor(ssh): split ssh-connection-utils into concretely named modules (#30488)
* refactor(ssh): split ssh-connection-utils into concretely named modules

- classifiers -> ssh-reconnect-error-classification.ts
- retry/timeout constants and sleep -> connection/ssh-connection-timing.ts
- buildConnectConfig -> connection/ssh-connect-config.ts
- credential callbacks -> connection/ssh-connection-callbacks.ts
- exec options and abort error -> connection/ssh-exec-options.ts
- wrapRemoteCommandForPosixShell -> ssh-posix-command-wrapper.ts
- shellEscape and shared/wsl-login-shell-command quotePosixShell (identical) -> shared/shell-argument-quoting.ts quotePosixShell

No re-exports are left behind.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: point the WSL git benchmark and the WSL lane route at shell-argument-quoting

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 20:48:05 -07:00
NeilandClaude 139bac88b6 refactor(ssh): move the ssh-connection and ssh-target cluster into ssh/connection/ and ssh/targets/ (#30479)
* refactor(ssh): move the ssh-connection and ssh-target cluster into ssh/connection/ and ssh/targets/

Pure git mv plus import and path-literal rewrites. ssh-connection-utils stays put; the next PR splits it.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(e2e): keep the release-checkout import at the v1.4.218 path

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 19:23:21 -07:00
Neil b1adb89157 refactor: import renderer runtime, shared remote-runtime and orcad helpers from their owning modules (#30370)
Removes the re-export lines in runtime-rpc-result (hasRuntimeRpcErrorCode), sync-runtime-graph,
sync-key, the terminal orphan-recovery modules, orcad-idle-exit, remote-runtime-subscription-transport,
two orcad modules and the ssh-connection test harness; importers and test mocks name the real modules.
2026-10-18 09:14:07 -07:00
Neil 1938bd055a refactor(ipc): group SSH and runtime-environment IPC handlers into ipc/ssh/ and ipc/runtime-environments/ (#30362)
git mv plus import, vi.mock and path-literal rewrites; e2e route regexes and the Windows host lane
filter follow the moved handlers.
2026-10-18 08:11:52 -07:00