Update, Rollback, Recover and Restart of a managed server already reset
its crash-loop brake as Connect does, but left the user's Disconnect of
the server and its SSH host in place, so the action was refused. They
now lift the holds Connect lifts (server and host Disconnect, cancelled
sign-in, re-pair cap) without dialing the host first, since Connect's
server-update check would run before the action (a Rollback would then
roll back the update it just made). Status resumes once the action ends.
A host with "Reconnect on launch" off holds its servers disconnected at
every launch. Their status now says so ("Reconnect on launch is off for
<host>. Connect to resume.") instead of claiming a manual disconnect the
user never made. The error code is unchanged.
Refs #38347
* perf(plugins): read catalog images in batched Git calls
Each catalog refresh ran one ls-tree and one cat-file per image or
manifest read. Batch every file an extraction round asks for into one
ls-tree listing and one cat-file --batch read, then rerun extraction
over the loaded files until nothing is missing.
* fix(plugins): bound speculative catalog blob prefetch
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
* perf(tab-bar): reuse each tab's activity status across unrelated store writes
* fix(tab-bar): retain only each tab's activity memo inputs
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Co-authored-by: Neil <neil@stably.ai>
Opening a branch in Source Control already compares it. The first status
and upstream loads then each looked like a HEAD move or a push and
queued one or two more full compares. Only treat known-to-different
values as a change.
Co-authored-by: m4air <m4air@Mac.localdomain>
Forge detection asks GitLab, GitHub, GitHub Enterprise, Bitbucket, Azure
DevOps and Gitea in turn, and each probe ran its own git remote get-url.
Share one read per remote across a single detection pass; the scope
closes when detection returns, so later reads see current config.
Co-authored-by: m4air <m4air@Mac.localdomain>
Each local terminal spawn with a Pi agent home rewrote three Orca-owned
extension files (~54 KB) synchronously on the main thread even when their
contents were already current. Compare the read we already do for the
ownership check and write only when the content differs.
Co-authored-by: m4air <m4air@Mac.localdomain>
The first agent-availability derive forced a second interactive login shell
moments after the Claude profile router's capture; settings changes did the
same per update. Recapture now accepts a capture started within the 10s
shell-environment TTL, and startedAfter lookups answer from a finished run
that started after the request.
Co-authored-by: m4air <m4air@Mac.localdomain>
The saved-password detector re-scanned every credential form on each
mutating page twice a second (forcing layout per input and walking the
page's buttons), but main only reads those snapshots for 2.5 s after a
login navigates. Main now tells each document at enable whether it wants
them.
Co-authored-by: m4air <m4air@Mac.localdomain>
* fix(ssh): stop restarting a managed orcad that keeps crashing, and say why
orcad now reports, in its health, a previous run that died on this boot
without releasing its instance lock. Graceful stops (idle exit, explicit
stop, update activation, rollback) release the lock, and a reboot changes
the boot id, so none of those count. After 3 such exits in 10 minutes the
desktop stops waking the server until an explicit Connect, and shows the
reason with orcad.log's path and tail instead of timing out.
Refs #37538
* fix(ssh): count fatal stops and failed starts; brake every automatic wake
- A fatal orcad stop (uncaught exception) keeps its instance lock, so the
next start reports it.
- A launch that never becomes ready counts once, not again when the next
orcad reports it.
- The tunnel port-recovery wake goes through the same brake.
- macOS and Windows hosts use OS uptime as boot evidence.
- Connect also drops a cached serving verdict.
Refs #37538
* fix(ssh): serialize crash-loop brake admission per host
Refs #37538
* fix(ssh): count reported Windows launch failures toward the crash-loop brake
Refs #37538
* fix(orcad): read the Windows launch report when the launcher exits nonzero
Refs #37538
* fix(ssh): ignore brake accounting from wakes begun before a reconnect
Refs #37538
* fix(ssh): count only launches that failed readiness, not inspection failures
Refs #37538
* fix(orcad): keep the command error when a Windows launch left no report
Refs #37538
* fix(ssh): surface the crash-loop reason from tunnel port recovery
Refs #37538
* fix(ssh): take the brake's reset epoch when a wake is requested
Refs #37538
* test(ssh): keep the cast on its SAFETY line
Refs #37538
* fix(ssh): tighten what the crash-loop brake counts and when it lets go
- A Windows host-policy refusal is not counted; its own guidance stays.
- The braked detail names the last failed start.
- A latch set on a run that served is released once that run's lock shows
it stopped cleanly.
- The update path's already-active wake goes through the brake.
- The fatal-stop test drops its shutdown timer.
Refs #37538
* fix(ssh): brake only a new launch, and count the wake after an automatic release
Refs #37538
* fix(ssh): refuse a braked launch at the fenced launch decision itself
Refs #37538
* fix(ssh): a refused launch also skips fence recovery, which can relaunch
Refs #37538
* test(ssh): a refused launch leaves fence recovery alone
Refs #37538
* fix(ssh): count failed recovery launches; dedupe only runs that ran
Refs #37538
* fix(ssh): explicit Update and Restart re-arm the crash-loop brake; release it after a reboot
- Update and Restart are user actions like Connect: they clear the brake.
- An auto-update that meets the brake on an already-current host is a
skip, never a recorded update failure that would block retries.
- The brake releases once the latched run's lock comes from an earlier boot.
Refs #37538
* fix(ssh): every explicit server action re-arms the brake; count failed candidate launches in recovery
Refs #37538
* fix(ssh): only gated wakes can return launch-refused
Ungated callers (version adoption, stale-lock reclaim, WSL, restart) pass no
launch gate, so their wake type no longer includes a refusal they cannot get.
Refs #37538
* fix(ssh): reboot evidence also releases a brake raised by failed starts
Refs #37538
* fix(ssh): release the brake only on a new boot of the machine that answered when it engaged
A leftover lock's boot id cannot tell a reboot from a shared-home lock
written by another login node or one older than the loop.
Refs #37538
* fix(ssh): a braked host seen serving can be released by that run's clean exit
Refs #37538
* fix(ssh): identify the rebooted machine by hostname and machine id
Login nodes behind one hostname share it, so the hostname alone cannot tell
them apart. Without a machine id the brake does not auto-release.
Refs #37538
* fix(ssh): only an answered run counts as served; a reboot must postdate the brake
A clone with the same hostname and machine id that booted earlier is a
different backend, not a reboot. Boot and read times come from the host's
own clock.
Refs #37538
* fix(ssh): only an answer received after the brake engaged marks its run served
A control-socket pong up to 20 s old may come from a run that has since
exited cleanly; counting it would let failed starts release the brake.
Refs #37538
* fix(ssh): say what to do when a host's server setup stops on a known cause
A managed SSH host whose server setup deferred always showed "Orca couldn't
start its server on this host. Try connecting again.", even when a retry
can't help (full disk, an interrupted update needing Recover, another Orca
runtime on the host's data). The deferral code was dropped before it reached
the host status.
Carry the code as an optional field on the unserved status, keep the retry
line only for codes a later connect clears, and name the fix for the ones a
user can act on. Older hosts and thrown errors carry no code and keep the
retry line.
Refs #38347
* fix(ssh): drop the Recover advice a first setup can't act on
Connect already runs activation recovery before deploying, and an unregistered
host has no Recover control, so that code now points at Details. Soften the
live-runtime line, keep waiting causes in the warning tone, and test the
published code and its remedy rather than English copy.
Refs #38347
* fix(ssh): treat an unreadable server build as needing Details, not a retry
Refs #38347
* fix(terminal): paint a new WebGL canvas inside a synchronized frame instead of leaving it blank
A replaced WebGL renderer's first paint went through terminal.refresh(), which
xterm buffers while DEC 2026 synchronized output is held. The old canvas is
already gone, so the pane showed an empty canvas until the program closed its
frame. Over a remote link Codex's frames often span packets, so revealing a
remote worktree flashed blank/content for under a second, only with WebGL.
Paint a just-attached or just-resized canvas at once. A canvas created while
its renderer is paused (hidden worktree) is remembered until its first paint,
so the reveal present paints it through the open frame instead of waiting.
Also drop the WebGL rebuild after every reattach replay: it worked around an
atlas problem that no longer exists and replaced the renderer on every reveal.
* fix(terminal): limit a never-painted canvas to one mid-frame paint
* test(terminal): prove a swapped WebGL canvas paints through spinner sync frames
* fix(terminal): clear the never-painted mark on any forced paint; drop e2e casts
* test(terminal): keep the spinner repro inside the flaky-pattern ratchet
orca account list called accounts.list with refreshUsage:false, so on an
idle headless host the JSON's rateLimits stayed null. JSON output now
asks the host to refresh only stale usage windows (the same
refresh-if-stale an accounts.subscribe stream runs), via a new optional
refreshStaleUsage param. The host waits at most 10s for it, so slow
inactive-account probes can't push the CLI past its request timeout;
they keep running and land on the next call. Older hosts strip the unknown field and keep
their prior no-refresh behavior; human output still skips the refresh.
Fixes#37256
* Add refresh action for folder project groups
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Refresh nested folder groups and flag incomplete scans
- Import new repos at any scanned depth into the deepest matching folder-scan sub-group
- Warn when the scan stopped early instead of reporting no new projects
- Match imported repos on the owning host only
- Show a loading toast while refreshing and pluralize the success message
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Claude in the Chat UI gets the same Artifact tools as Claude in a terminal: Orca sets CLAUDE_CODE_ARTIFACT=1 for chat launches unless the user already set a value.
Co-authored-by: Luke Ellison <lukeellison4@gmail.com>
* fix(native-chat): keep a Claude chat's effort changeable after its first turn
After its first turn a Claude chat saves the model id Claude reports it ran
(claude-opus-5-5), while the model list names that model only by its alias
(opus). Every capability lookup matched on the row id, so the chat's model
had no effort choices and effort turned into display-only text on desktop
and phone.
A listed row now describes a model id by its own id or by the model it
resolves to. The match supplies capabilities only; the chat keeps showing and
sending the exact model it ran, so it never drifts when the alias moves. The
host applies the same rule to the row it adds for the chat's model, for
clients that read the answer's models.
* test(native-chat): cover exact-row precedence for a described model id
* fix(native-chat): remember an after-reply effort without changing the new-chat model
An effort pick in a chat on the resolved id it ran is remembered under the
listed row that runs that model and leaves the new-chat model untouched, as
the common pattern keeps the sticky model and updates only its options. The
resolved id is never stored as a default.
* refactor(native-chat): move the remembered-pick model choice into its own module
* fix(native-chat): keep a held effort pick from adopting a new-chat model
* test(native-chat): run the held-effort case as a Claude launch
* revert(native-chat): drop the host-side capability copy no live answer can use
Claude's live session listing carries no resolvedModel, so the host never matched there, and phones draw the host-served chat bundle, so every client already runs the shared lookup.
* Key Claude's model rows by the model each one runs
Claude's listing named rows by alias (opus), while a chat saves the model
it ran (claude-opus-5-5) after its first turn, so equivalent chats held two
kinds of model value and a chat's effort could go read-only. Rows are now
keyed by the versioned model they run, the common pattern: a pick saves the
same id the CLI reports. Aliases stay as their own rows with their own
effort levels; the picker lists one only while the chat is on it or a
search matches it, and the phone list leaves them out. A saved alias is
kept as the user picked it. A configured alias default marks the versioned
row it runs, and an effort picked in an existing or resumed chat no longer
changes the model new chats start on, whatever ids the chat and listing use.
* test: give the delayed-report alias test's timeout its reason
* Remember a new chat's picks by its first turn, and keep aliases reachable everywhere
- A chat counts as existing once it has a turn or a send, or when it was
opened rather than launched here, not once its launch published: an empty
new chat's effort pick adopts its model for new chats again, and every
agent's existing chat keeps the remembered new-chat model.
- Only a row that names itself as the model it runs covers an alias, so an
older host's alias rows stay listed beside a chat's stand-in row.
- The phone lists an alias of a listed model behind a More models toggle.
- The terminal-chat and commit-message model lists carry each row's
resolvedModel, so they list aliases the same way; an older remote that
omits it keeps its full list.
* fix(mobile): keep the More models toggle's state without an effect reset
* Keep commit-message aliases selectable and their resolved model out of saved settings
The Source Control model Select has no search, so it lists alias rows after
the versioned ones instead of hiding them. Discovered models are saved
without an alias row's resolvedModel, and the refresh compares that same
saved shape, so the settings cache gains no field.
* Drop an unrelated audit-tools lockfile change; reset the phone's More models toggle when its drawer closes
* test: build the saved-models test's settings without a cast