The background wake a phone triggers on the desktop host resumed slept agent
records with no host PTY inventory check. In a worktree the desktop is not
looking at, a pane whose PTY the daemon still runs (e.g. after a renderer
restart) reads as unowned, so the wake forked a second claude --resume onto
the live transcript. Route it through the activation gate, which resumes only
records whose PTY the host inventory shows exited and declines when the
inventory cannot be read. (STA-3859)
* fix(status-bar): show usage from the host that owns the accounts
With an SSH-managed or paired Orca server owning the focused workspace's
accounts, the usage badges read that host's accounts snapshot instead of
this device's local poll. An older host that publishes no usage reads
"Usage unavailable", and loss of contact is an unverifiable verdict.
* fix(status-bar): keep remote usage fresh and honest across stream loss and owner switches
* fix(status-bar): never borrow the viewer's setup flags for a remote usage owner
* fix(status-bar): an outage stops a still-fetching remote usage slot from loading
* fix(status-bar): route usage by the focused workspace owner and fix a narrowing error
* fix(serve): drive hook titles and first-work rename without a window
Headless orca serve never opens a window, and both the synthetic agent titles
(Hermes readiness) and first-work branch/workspace rename were driven only from
the window's hook listener. Subscribe both to the hook store directly at
startup, and feed synthetic frames to the runtime tracker before the window
check, so they run in serve and desktop alike.
* fix(serve): narrow the hook event for the rename entry
* fix(serve): a new turn's working title reaches the tracker unseen; keep spinners across window reopen
A moved host's workspaces are owned by its managed server, so the Ports
panel showed the server's port scan and dropped the forwarding list and
Add button, though the SSH connection under the server's tunnel still
carries and restores this client's forwards. Show that host's forwards and
Add control above the server's scan. A forward listens on this machine,
so its rows open in the system browser here.
* fix(remote-files): open HTML, PDFs and images too large for one reply on a paired runtime
files.read stops at 512 KiB and files.readPreview at about 3 MB (one 4 MiB
reply, after base64), so larger files on a paired runtime failed to open.
Read them in chunks with the files.stat and files.readChunk methods hosts
already have, and refuse a save the host's 1 MiB frame cap would answer by
closing the connection.
Fixes#29306Fixes#26062
Co-authored-by: sotashimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
* fix(remote-files): keep a UTF-8 BOM when paging a large remote text file
* fix(remote-files): pin a paged read to one pairing and compare ctime
* fix(remote-files): pin the pairing before a remote read's first request
---------
Co-authored-by: sotashimozono <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
* fix(orcad): log and survive recoverable process errors, stop cleanly on fatal ones
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(orcad): a fatal fault during an existing stop still exits as a failure
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(browser): the remote browser tunnel goes through the server's outbound proxy
A client-rendered browser tab on a paired server reaches sites through a
tunnel the server opens. The server dialed each destination with a bare TCP
connect, so a network that only allows traffic through a proxy refused it,
while the server's own browser (which honors the proxy) worked.
The native tunnel connector now picks the upstream proxy the same way the
server's browser sessions do: Orca's proxy setting first, else
HTTPS_PROXY/ALL_PROXY/HTTP_PROXY, each with its own bypass list, never for
loopback. HTTP(S) proxies are reached with CONNECT (with basic auth from the
URL), a no-auth SOCKS5 proxy through the existing SOCKS client; a proxy kind
it cannot speak fails the open instead of leaking around the proxy.
Fixes#29154
* fix(browser): match Chromium bypass semantics for Orca's proxy setting; keep HTTPS-proxy tunnels half-open; tolerate raw percent in proxy credentials
* fix(browser): full-glob bypass rules and trailing-dot loopback names stay off the tunnel proxy
* fix(browser): scheme-qualified and IPv6 CIDR bypass rules match like Chromium
* fix(browser): expanded IPv6 loopback from the SOCKS front end stays off the tunnel proxy
* test(browser): loopback spellings the SOCKS front end hands the tunnel stay direct
Remote panes cannot echo a keystroke before one round trip, so past ~150ms
RTT their own typing fell out of the fast paint lane. Each pane now keeps the
recent minimum input-to-output delay and adds it (clamped to 500ms) to the
redraw and synchronized-frame windows; local panes measure ~0 and keep the
existing windows.
Linear: STA-5314
* fix(daemon): a daemon update never hands a live old-daemon session id to the new daemon
Refs #26296
* fix(daemon): reconnect an older daemon through its inventory before refusing a spawn
* fix(daemon): reattach the current daemon's own session before consulting older daemons
* fix(daemon): reconnect the current daemon too before an older daemon can refuse its session
* fix(terminal): read a Windows ConPTY terminal as painted, not with PSReadLine's overwritten history prediction
* fix(terminal): bound the ConPTY rendered read, honor visible-only probes, and fill the line limit
* test(serialize): record the ConPTY capture's pre-existing wrap-pending cursor restore
* test(agent-hooks): guard that a slow last-status rename never stalls hook traffic
Regression guard for #22804 (STA-8446).
* test(agent-hooks): flush while the older rename is held so the stale-rename race is exercised
ORCA_TERMINAL_MEMORY_MAX and ORCA_TERMINAL_MEMORY_SWAP_MAX set MemoryMax and
MemorySwapMax on each terminal's systemd user scope. Off by default, applied only
after the probe scope shows the kernel enforcing them, and dropped (logged) where
the host cannot, so terminal scopes never turn off because of them.
Refs STA-3430
* fix(orcad): seal integration credentials on the headless server instead of storing them in plaintext
The Node SecretStore always reported encryption unavailable, so every
credential writer on orcad fell back to plaintext. orcad now seals with
AES-256-GCM using a host-owned master key kept in the OS keyring (macOS
Keychain, Linux Secret Service) when reachable at first start, else in a
0600 profile file bound to the machine ID. A key that cannot load fails
closed instead of downgrading. Existing plaintext credentials are sealed
on startup without loss.
* docs: describe how the headless server seals credentials and its limits
* fix(orcad): unique credential staging files, no keyring items for temp profiles, isolate startup test
* fix(orcad): keep the credential key with the credentials, one record per machine
* fix(orcad): never re-key over an unreadable record, serialize first-start key creation, refuse an unrestricted key file
* fix(orcad): verify a key record's Windows ACL and wait out a concurrent first start's keyring write
* fix(orcad): keep a keyring claim and fail closed when its store result is uncertain
* fix(credentials): restrict the staged file's Windows ACL before it replaces a credential
* fix(orcad): a temporary profile never creates a key in, or re-seals, the real credential home
* ci: baseline the two single-file credential ACL restrictions
* fix(credentials): stage Windows credentials born restricted and verify the key record asynchronously; document rollback
* test(bitbucket): inject the write failure into the Windows staging writer too
* fix(notifications): a reconnect replay no longer re-fires task-complete notifications
Carry the host's isReplay flag through the agentStatus:set IPC payload so the
renderer seeds completion tracking from replayed rows, as it already does for
the startup snapshot, instead of treating each cached done as a new turn.
* test: drop a type assertion from the replay notification test
A project set up here and on a paired server has repo rows on both hosts.
Local worktree rows are left unstamped, so their owner lookup fell to those
repo rows, found two hosts, and gave up; openFile then bound the tab to
whichever server was focused, which cannot find the workspace.
Listings stamp every paired-server row with its host, so an unstamped row
of such a project was listed here: route it to this machine.
Fixes#25467
* fix(ssh): hold the password challenge until the deferred key has been tried
When an agent is configured, the first attempt carries only unencrypted
explicit keys and leaves default-name (~/.ssh/id_*) and encrypted keys to the
no-agent retry. keyboard-interactive still sat at the end of that first
attempt's ladder, so a host that collects its password through
keyboard-interactive showed the Password: dialog before the deferred key that
authenticates had been tried. Since #15588 that prompt goes through the
password dialog and caches the answer, and cancelling it marks the attempt
cancelled, which also skips the key retry, so the connection fails instead of
falling back to the key.
An attempt that carries no key but defers some now skips keyboard-interactive
when the host lists it as a first-factor method next to publickey. When the
host does not list it, the challenge can only be a second factor after the
agent key partially succeeded, so it stays in the ladder. When the host does
not list publickey, the deferred key cannot help, so it stays too. Attempts
that carry a key keep the challenge: SshConnection never retries them with the
deferred keys, and the password retry reuses their config.
The deferral applies only when every deferred key parses as a private key that
needs no passphrase. With an encrypted one the retry would ask for its
passphrase instead, so skipping the challenge would only swap one dialog for
another, and cancelling the passphrase dialog fails the connection rather than
reaching the password flow. A file that is no usable private key (corrupted, a
public key under a private-key name, an unsupported type) makes ssh2's
connect() throw before the retry sends any auth request, which is not an auth
error, so the connection would fail without ever offering the challenge. Those
targets keep the origin/main ladder.
The cost: when the host rejects the deferred key, its password challenge now
arrives on the key retry, one failed handshake (plus a respawned ProxyCommand,
if configured) later than on origin/main.
Constraint: ssh2 1.17 hands an agent key's USERAUTH_FAILURE to the next agent key before recording partialSuccess, so after an agent partial success the auth handler sees the pre-agent method list with partialSuccess=false
Constraint: ssh2 parses config.privateKey before any agent auth can run, so default-name and encrypted keys stay deferred to the no-agent retry
Constraint: SshConnection runs the no-agent key retry only when the agent-first attempt carried no key
Rejected: drop keyboard-interactive from the deferring attempt's whole initial ladder | ssh2 hides an agent partial success, so publickey,keyboard-interactive hosts lose their second factor
Rejected: defer whenever any key is deferred | a mixed-key attempt gets no key retry, so a keyboard-interactive password host would never see the challenge
Rejected: defer for encrypted keys too | the Password dialog becomes a passphrase dialog, and cancelling it fails a connection that origin/main completes
Rejected: check the deferred keys only for encryption | a garbage or public key file aborts the retry with a non-auth error, failing a connection that origin/main completes
Rejected: run the no-agent retry even when no key is waiting on disk | adds the same extra handshake for agent users who have no key to try in between
Directive: keep deferKeyboardInteractive in step with the key-retry guard in SshConnection.attemptConnect
Confidence: medium
Scope-risk: narrow
Reversibility: clean
Tested: pnpm test src/main/ssh; real-socket fixtures for a first-factor challenge on a publickey,keyboard-interactive host, an encrypted default key, and an agent partial success followed by keyboard-interactive; unit tests for a garbage or public key file under a default key name; mutation checks (no deferral, no key check, encryption-only check, publickey guard removed, whole-ladder withholding, any-key-deferred condition) each fail a new test
Not-tested: the reworked revision against a live host
Related: #15588
* fix(ssh): keep an agent key's second-factor challenge when the host first offered both methods
* fix(ssh): defer the password challenge only for a single waiting key
* fix(ssh): read the deferred key only after the agent attempt reaches the challenge
---------
Co-authored-by: Sangrak Choi <kars@kargn.as>
A legacy project setup's id is its repo id, so a moved SSH host's
retained setup and the managed server's setup share one id. The Run Now
check took whichever came first and reported a host mismatch. Pick the
setup whose host matches the run context.
Every session the scanner read only partly (for example a Codex transcript on an
SSH host with a record over the 10 MiB cap) adds its own notice row above the
list, outside the list's scroll area. With dozens of them the panel filled with
notices and the sessions could not be reached. Notices now sit in one bounded
area that scrolls on its own.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(serve): reap orphaned processes when orca serve is PID 1
* fix(serve): keep AppImage, eval launches and exit status intact under the PID 1 reaper
* fix(browser): defer publication cleanup until native destruction completes
* test(browser): reproduce the serve tab-close SIGSEGV with a hidden page window
Headless serve backs each page with a hidden top-level BrowserWindow, whose
Aura view is what crashes; the <webview> guest the fixture used never reached
that path, so the test passed against the original code. With the serve shape
the test segfaults (fault 0x1268, RIP +0x55a8d7b) without the fix.
* test(runtime): wait for the deferred publication restore after paired create and close
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
linear-sdk.ts loads the SDK through createRequire, which the orcad bundle
cannot follow, and nothing staged the package beside it. Bundle the SDK
into node_modules/@linear/sdk/index.js at build time and list it as an
orcad artifact so templates and installs carry it.
Co-authored-by: thukabjj <31043997+thukabjj@users.noreply.github.com>
Orca keeps a Claude permission wait visible until it sees the exact tool it was
waiting on resume. A tool that fails reports PostToolUseFailure, whose tool
name and input Orca clears, so the resume check never matched and the pane kept
showing "needs permission" while Claude was already working. The exact
tool_use_id and owner now settle the wait for a failure as well.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(ssh): flag a macOS host whose managed server outlived its launching SSH session
macOS Local Network privacy pins a nohup-launched orcad to the sshd-session
that started it; once that session ends, its terminals lose LAN access (#25934).
A macOS launch now records that session, a connect reports it when it proved
exited, and the host status offers a restart when no terminals are running.
* fix(ssh): retire the idle terminal daemon on a launch-session restart
* fix(ssh): keep the fence on an unsettled restart stop, and report a restart that can't hold its session
* fix(ssh): carry the launch session forward while the terminal daemon survives; never redial for the check
* chore(i18n): add the launch-session warning and restart strings to the English catalog
Every worktree switch on a paired desktop lists that worktree's session tabs.
session.tabs.list was pinned to the one-shot transport so snapshot recovery
keeps working while shared-control streams reconnect after a host restart, so
each switch opened, upgraded and E2EE-authenticated a brand-new WebSocket just
for that read: about three extra round trips per switch. Use the shared-control
socket when it is ready, and keep the one-shot path whenever it is not.
Refs #26785
* fix(remote-terminal): a headless host keeps a parked terminal at its viewer's grid
Parking a paired pane closes its stream, the PTY's last viewer. The floor then reclaimed the PTY to the host's geometry, which on a windowless orca serve/orcad host is the 120x40 spawn grid nobody sees. Every reveal then fetched a snapshot at 120x40 and reflowed it to the pane, and a TUI redrew twice. With no host pane to give the grid back to, the floor now keeps the last owner's grid; the parked owner's reserved seat resumes it without a resize.
* test(remote-terminal): a phone or another viewer still takes a parked terminal's grid on a headless host
* fix(remote-terminal): a phone leaving a parked headless terminal restores the parked grid
* fix(remote-terminal): keep the parked desktop grid as the restore baseline even while a phone fits
* test(remote-terminal): tie each PTY size probe to its own reply
Every time a paired desktop remounts a remote terminal pane (revealing a parked
worktree or tab), it re-sends the layout the host gave it. A headless orca serve
or orcad persisted the workspace session and republished the whole workspace's
tabs to every paired client for each of those identical writes. Compare the
edit with what the host already holds and skip both when nothing changed.
Moves terminal-layout-equality to src/shared so the host can reuse it.
Refs #26785
* fix(runtime): route a CLI close of a paired host's terminal to that host
orca terminal list on a desktop lists terminals a paired runtime runs, but
orca terminal close on one of those handles stopped the remote PTY id on this
machine, which cannot own it, and answered terminal_stop_unverifiable while
the process kept running on the host. closeTerminal and closeTerminalTab now
send the close to the owning runtime first and return its receipt; when that
host is unreachable the close fails and the terminal stays visible.
* fix(runtime): revalidate the pane after a paired-host close, and read the owner's stale-handle answer
Codex review: a pane rebound while the host call was pending had its new
occupant retired, and an owner that no longer knows the handle answers
terminal_handle_stale, which rejected instead of retiring the mirror.
* fix(runtime): refuse a pinned paired-host tab before asking its host to close it
Codex review: the renderer's pin guard runs at the local commit, after the host
had already stopped the process.
* fix(terminal): a reveal after hidden output was dropped always restores it
A restore that failed while its pane was hidden zeroed recovery and wrote
its loss banner where nobody could see it, so the reveal skipped the
restore and the gap stayed silent and permanent. Stay armed instead and
spend one fresh snapshot on reveal. Main's drop latch now retires on the
renderer's paint ack rather than on unhide, so a hidden pane that retires
cannot spend the only marker on a handler that is already gone.
Fixes#15504
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(terminal): dedupe a disclosed hidden-output loss by drop episode
The unhide marker carries main's emit-time output sequence, which visible
output advances without any new drop, so a seq comparison re-bannered the
same gap. Main now tags gate markers with a drop episode, and a drop after
an unhide starts a new episode that the in-flight restore's ack cannot
retire.
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* fix(terminal): a restore ack retires only the drop episode it covered
An older restore still in flight when a newer hide/drop/reveal cycle ran
could ack and erase the newer episode's drop memory. The renderer now
captures the latest reported episode before fetching and names it in the
ack; main retires the latch only on a matching episode.
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
* test(terminal): pin hidden-restore recovery against a drop-reporting host (#29253)
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
---------
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
The startup residue sweep deliberately never reaps runtime:* rows (#17776), and
the renderer never told main when a paired host confirmed a project removal, so
those rows outlived every remote delete. Removal now calls repos:removeForHost
for the owning runtime host once the host confirms, and main prunes that
removal on the paired host's partition only.
* perf(worktrees): a Smart-sort save on a paired host no longer refetches every repository or schedules the next save
The host now publishes the saved sortOrder values on the order-only
worktreesChanged event, so a new desktop patches its rows in place instead of
re-reading each repository. A refetch whose only difference is sortOrder no
longer bumps sortEpoch, so an older host's save cannot trigger another Smart
re-sort and save.
Refs #27268
* fix(worktrees): keep lineage retry and receipt order for published sort-order saves
* fix(worktrees): refetch rather than apply sort-order values published on an earlier connection
* fix(worktrees): fence published sort orders on the event-stream subscription key
* fix(worktrees): refetch a published sort-order save when its rows were never loaded
listRepoWorktreeGraph answered [] for a runtime- or ssh-owned repo, so hosted
review told the user a live remote worktree did not belong to its repository.
It now throws the same WorktreeCatalogUnavailableError as listRepoWorktrees.
Fixes#18272
* fix(terminal): a cold-parked terminal reveal paints once, at the pane's own size
A structural replay (park reveal, reattach, hidden-output restore) clears the
pane, resizes xterm to the snapshot's grid, replays, and only then fits back to
the pane. Every step was presented, so a reveal showed a blank pane, then the
host's grid in the top-left, then the real fit. The screen is now withheld
behind a copy of its last frame until the transaction and its fit finish,
capped at 1 s so a deferred fit can never leave a live pane unpainted.
Pane-level half of #22728, plus a hold cap and a paired-runtime reveal e2e.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* fix(terminal): keep a WebGL screen shown when its frame cannot be copied, and keep decorations positioned
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
---------
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
The runtime socket transport reaped connections after 30s of raw byte
silence, and Node's idle timer counts bytes — so a slow dispatch like
worktree.create on a large repo, which is not a long-poll and sends no
keepalives, read as a dead peer: the server destroyed the socket mid-
request, the handler kept running (the worktree got created), and the
CLI reported runtime_unavailable and told the user to restart. Callers
then retried, leaving orphaned worktrees or two agents on one task
(#25830, the root behind #15663/#23911/#20108 symptoms).
The reap timer now suspends while dispatches are pending and rearms
when the last one cleans up. Quiet connections with nothing in flight
still reap after exactly the same 30s, and a dead peer on a local
socket is delivered by the kernel as close/error, which the existing
cleanup already handles — so the reaping duty is unchanged where it
mattered.
Fixes#25830
Generated-by: GLM-5.3-Flash (ZCode)
Co-authored-by: ggbdpq <ggbdpq@gmail.com>
* fix(github): resolve SSH host aliases for account binding
An origin like git@github-personal:owner/repo kept the alias as the
required gh host, so the Settings account list matched nothing.
* fix(github): keep github.com filter for resolved aliases and resolve in the gh runtime
Real canonical keys carry github.com, so an alias resolving to github.com
must filter to github.com like a direct remote. Resolve via the runtime-scoped
resolveSshConfigHostname (WSL distro aware) and keep Enterprise hosts exact.
* fix(github): expand SSH aliases only for SSH-transport remotes
* fix(github): resolve the SSH alias with its original case
---------
Co-authored-by: orchardprior <342342873+orchardprior@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>