* fix(wsl): retry a git read when wsl.exe hits a transient service error on a booting distro
On a cold distro, concurrent first wsl.exe launches intermittently fail with
Wsl/Service/E_UNEXPECTED before the guest runs. The bridge's worktree listing gave
up on the first one, so a WSL project showed 0 workspaces until the user acted.
Reads now retry that one error up to three times (0.25, 0.75, 1.5 s).
* fix(wsl): bound transient-error retries per distro, honour aborts, keep the direct git path
Review follow-ups: after one read spends its retries the distro fails fast for 5 s
instead of every queued read sleeping in its admission slot; an abort or a disposed
caller ends the backoff at once; a transient service error no longer falls back to
the login shell and turns the direct read path off.
* test(wsl): drop type assertions from the transient-retry test
* fix(remote-terminal): calm, honest reconnect status with one notice per server
* fix(remote-terminal): keep the SSH sign-in wait immediate under the calm reconnect banner
* fix(remote-terminal): a surface that loses its turn drops its notice at once
* fix(remote-terminal): resolved action cards and handed-off notices leave at once
* fix(remote-terminal): only the leader at the end of an outage holds its notice
* feat(telemetry): count Windows-drive WSL repos with WSL-made worktrees
The daily wsl_usage_snapshot gains projects_wsl_windows_drive_wsl_worktrees: of the
Windows-drive repos that run Git in WSL, how many have a linked worktree WSL Git made
(a Linux-path link). Git for Windows can't take those over, so the D1c conversion
refuses them; this sizes that class before the bridge is removed. Bucketed count only,
read from the link files with no Git spawned; optional in the schema so older payloads
still validate.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(telemetry): don't count Windows Git's //server/share worktree links as WSL-made
Review: a UNC link starts with '/' too. Reuse the prune guard's classifier, and note
that a repo whose .git is a file reads as having no worktrees (no Git is spawned).
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(terminal): keep xterm parse barriers and the output behind them across a resize
Orca waits for xterm to finish parsing by writing an empty string with a
callback. In xterm 6.1, resize() drains the write queue through
WriteBuffer.flushSync, whose loop stops at a falsy chunk and clears the rest of
the queue. A resize while such a barrier was queued therefore dropped its
callback and every chunk written after it: replay guards and output waits fell
back to their stall timers, and that pane output never reached the screen.
The replay guard, output scheduler and write-pipeline probe now write a
zero-length Uint8Array through writeXtermParseBarrier. It parses as nothing, but
it is a truthy chunk, so the flush keeps going. Test fakes that recognised a
barrier by '' now check for a zero-length write. Regression tests use real
@xterm/headless and fail before the fix.
Fixes#26950
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(terminal): verify parse barriers across installed xterm runtimes
Exercise the existing contributor workaround against both browser and
headless packages through replay waits, scheduler waits and health probes.
Check FIFO callbacks, following bytes, parser tails and no-resize controls.
Register the bounded resize invariant as an experimental reliability gate.
Make terminal test writers accept string or Uint8Array and preserve textual
captures through the existing fixture conversion. Keep the original PR's
production implementation and contributor ancestry unchanged.
* test(terminal): complete byte barriers in unhide restore fixture
---------
Co-authored-by: outof-place <contact@outofplace.space>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(windows): include registry addon in relocated daemon host
* test(windows): preserve registry path in native-read child script
* fix(ci): match registry gate command to recorded validation
* fix(worktrees): don't prune worktrees the other Git (Windows or WSL) created
On Windows one repo can hold linked worktrees written by both Git for Windows
(link C:/...) and WSL Git (/mnt/c/..., /home/...), e.g. after the project runtime
toggle switched it. Each Git reads the other's links as missing, and the
`git worktree prune` Orca runs after removing a worktree (and before deleting a branch
held by one) deleted those worktrees' metadata, leaving checkouts git no longer knows.
All five prune sites now go through pruneGitWorktrees, which reads the link files first
and refuses (through each site's existing failure path) when any link is foreign to the
Git that would run.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(worktrees): lock the other Git's entries for the prune instead of skipping it
Review: refusing the whole prune broke stale-registration recovery and left branches
undeletable in mixed repos, and reading <cwd>/.git/worktrees missed bare and linked
checkouts. Now the other Git's entries get a marked lock file for the prune (git never
prunes a locked entry; a user's existing lock is kept), the rest prune as before, and
the admin dir comes from rev-parse --git-common-dir once routing is settled.
//server/share links read as Windows Git's.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(worktrees): drop a type assertion from the prune guard test
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(worktrees): serialize guarded prunes per repo and reclaim stale guard locks
Re-review: two prunes in one repo could interleave so the first's unlock let the
second prune the other Git's entry, and a crash between lock and unlock left a lock
read as the user's forever. Prunes now chain per admin dir, and a lock carrying the
guard's own reason is reclaimed.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(worktrees): assert the overlapping prune waits, not only its outcome
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix(dashboard): skip hidden pop-out view transitions
A column change in a hidden Agent Dashboard pop-out started a View
Transition whose ready promise rejects with InvalidStateError. Nothing
observed that promise, so each move became an unhandled rejection.
Skip the transition while the document is hidden, and consume ready
when a transition still starts.
Fixes#21323
* fix: preserve visible dashboard transition errors
* test(dashboard): preserve unrelated hidden transition errors
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(editor): enable Lean syntax highlighting
* fix(editor): enable Lean syntax highlighting
Adapt #31930 onto current main and retain its bundled Lean grammar repair. Register the lazy TextMate provider and cover detection, proof syntax, nested comments, and bounded tokenization.
* test(editor): verify Lean highlighting in the full app
---------
Co-authored-by: Neil <neil@stably.ai>
Client control-socket closes and server heartbeat reaps only reached the
console, which a packaged app never shows. Each control-socket close now
writes a remote-runtime.control-socket-close span (cause, close code,
error code, last keepalive RTT, socket and ready uptime). Each server reap
writes remote-runtime.server-socket-reaped. Both land in the main trace
file that Send app diagnostics collects. Keepalive pongs now measure RTT,
exposed as lastRttMs and lastCloseCause on the shared-control diagnostics.
On macOS wake and network online events every remote-runtime socket was
probed with an 8 s deadline and closed on a miss. Relayed or lossy links
still rerouting after a wake lost connections that would have survived.
A miss now re-pings and leaves the socket until the normal 25 s keepalive
window ends; the probe is skipped when data arrived in the last 3 s.
* fix(native-chat): name workspaces by folder in the resume dialog while workspaces load
On restart the resume dialog opens before the full workspace list loads, so
every row it could not look up showed its raw repo-qualified id. Fall back to
the folder name the id carries, as the status bar and Automations already do.
* fix: distinguish unknown resume workspaces with matching folder names
* refactor: extract resume workspace display derivation
* fix: distinguish resume workspace control names
Exiting a paired host tab's original pane moved the host's active leaf to the
surviving split. The client's reconciliation effect followed it with
setActivePane, and that persisted every still-mounted pane, writing the retired
leaf (whose remote PTY had not ended yet) back into the layout. The leaf was then
no longer retired, so its pane stayed forever. Any other persist in that window
(insertions, clicks, title edits, divider drags) could do the same.
persistLayoutSnapshot now drops the leaves the host retired, using the
retireLeavesFromTerminalLayout main already uses, moved to shared. The retired
set it reads is the one the reconciliation effect tracks.
* ci: fail when a checked-in test file has no runner
Two node:test files under .github/actions lost their runner when #31792
trimmed cloud pretest, and the two monaco .bench.ts budgets were never run
by any CI job. Wire both back in and add a static check that every
test-like file is collected by a declared runner or imported by a
collected entry (the src .spec.* suite-module convention).
* ci: ignore type-only imports and quote the lease glob for cmd.exe
* ci: parse type-only imports, cover UNIT_EXCLUDE, route cloud runner edits
Type-only imports are now dropped with the TypeScript parser, so a type
alias before a runtime re-export no longer hides it. UNIT_EXCLUDE files
must be named by a pr.yml or unit-tests.yml step. A cloud-only diff that
adds a test or edits a runner config now runs the static job. The monaco
benchmarks stay manual (pnpm test:perf:benchmarks) until a CI runner
proves their wall-clock budgets.
* ci: require UNIT_INCLUDE names in dedicated jobs and erase typeof import()
* ci: resolve each runtime import to the one file Vite loads
The unit dependency graph keeps every plausible candidate so it can
invalidate conservatively, which cannot prove a suite runs: an import of
./case.spec counted both case.spec.ts and case.spec.tsx. Resolve with
Vite's extension order instead.
* ci: take runtime imports from transpiled output
Imports used only as types are erased without a type modifier, so
parsing for type-only syntax still followed them. Derive edges from
ts.transpileModule output, which erases what Vitest's transform erases,
and only transpile files that can reach an uncollected candidate.
* ci: fix the agent-status benchmark config and route all cloud code
The agent-status benchmark config inherited the unit projects, whose
includes override its own, so it ran the whole unit suite and never the
benchmark. Clear projects as the performance config does. Route every
cloud code file to the static job, since content-detected tests can have
any name.
* A Claude chat shows the turns made outside it
When a Claude chat's agent next opens, turns written to the same
conversation elsewhere (claude --resume in a terminal) are appended once,
in order, under the existing "Continued outside this chat." divider.
Streamed reply and thinking rows are now keyed by their source block
(message id and block index), which Claude also saves on each transcript
row, so the chat can tell which saved rows it already shows. The read goes
back from the end of the transcript only to the chat's saved turn-end leaf,
replays the rows through Claude's own translator in an isolated sink, and
commits them in one settlement that writes nothing if the chat moved.
Imported prompts are display-only and never settle a send; any failure
opens the chat normally.
* test(claude): name the transcript row shapes the catch-up test builds
* fix(claude): read a catch-up row's uuid once as a string
* fix(claude): parse transcript tail rows without type assertions
* refactor(native-chat): share the catch-up open runner across agents
The Codex catch-up's agent-neutral parts move to one module: the open-time
envelope (its own deadline and cancellation, never gating the open), the
bounded staged batch, the single settlement commit that re-checks the
journal before writing, and the "Continued outside this chat." divider.
Codex keeps only how it reads its saved turns and what the chat already
holds. No behavior change; the other agents' catch-up builds on this
instead of copying it.
* refactor(claude): catch up through the shared history catch-up runner
* chore: remove unrelated pnpm executable metadata
* refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks
* chore: remove regenerated pnpm executable metadata
* fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written
A journal write barrier that never settled after the import was admitted held
the provider's open past the catch-up deadline. The wait now ends with the
deadline or the open's cancellation, like every other step.
* test: type the history catch-up sink fixture without assertions
* fix(native-chat): bound and revoke history preparation
* test(claude): expect source block identity in stream cadence
* fix(claude): preserve final output and history coverage
* feat(native-chat): validate expected saved items during history catch-up
* fix(claude): integrate conditional history settlement
* fix(claude): use new content and provider prompt identities for catch-up
* fix(claude): retain qualified completed leaves through session exit
* fix(claude): preserve unresolved membership for recovered follow-ups
* test(claude): use fork epoch reason for rewind preservation
* fix(claude): consolidate translator sink type imports
* fix(claude): preserve source message times during catch-up
* fix(claude): narrow staged history mutations to items
* test: bound Claude retained-leaf catch-up work
* fix(claude): settle imported work at source boundaries
* test(claude): keep shared journal harness portable
* test(mobile): await editor listener cleanup before snapshot
* test: isolate retained slice measurements and accept closed socket EPIPE
* fix(web): chat attachments from the browser's file picker and drops upload to the paired server
In the web client the chat's attach button resolved no files and a dropped
file was refused as unresolvable, so neither ever attached. The browser now
opens its own file picker, and a drop on the chat hands over its File
objects, each held under a token whose last segment is the file's name. The
chat's upload reads the held file and streams it in chunks over the paired
server's existing agentSessionAttachment upload RPC, as the browser's image
paste already does.
* fix(web): release held browser files where an attach ends, never by eviction
An eviction cap could drop the files of a batch still waiting to upload when
another pick or drop came in. Every pick and drop now ends in the composer's
one delivery step, which releases whatever it left unclaimed.
* fix(web): release held browser files a disabled composer or detached drop owner never delivers
A pick that returns to a disabled or unmounted composer skipped the delivery
step, and so did a queued drop whose owner detached; both now release their
held files. The composer-workspace comparison moves beside the other
attachment ownership checks.
On macOS, orca serve vetoes an unmarked app.quit() (#15537), so Playwright's
close never finished and its fallback force-killed the whole process tree,
including the detached PTY daemon's terminals. The restart spec's fixture
process got SIGTERM and the spec failed on every macOS run.
Restart now stops serve the way a supervisor does (SIGTERM, a marked quit),
and the restart spec no longer shows or focuses its client window.
* fix(terminal): a split of a paired-server pane still attaching starts on the server
A pane of a server tab whose own attach is in flight has no transport id, so its split fell back
to a local split. Only the server starts terminals in its tabs, so the new pane stayed blank.
Split from the terminal the server already published for the pane, or wait for it.
* fix(terminal): tell the user when a held split never starts, and keep focus where they went
A split held for the server's source terminal now gives up when the pane, its tab or the pairing
goes, toasts when the server never publishes it, and lands without focus if the user moved on.
* fix(terminal): a held split runs only on the server the gesture targeted
Check the workspace still belongs to that server before reading the source binding, and accept
only a binding that names it, so a hydration swapping both cannot redirect the split.
* refactor(terminal): route an unattached server pane's split straight to the server
Drop the held split. The split uses the terminal the server published for the source pane, and
when there is none yet it says so at once instead of queueing a gesture that needs its own fences.
* fix(terminal): a server-tab pane never splits locally
Refuse every split the server cannot take for a pane of its tab, and say whether the host is
disconnected or the source terminal has not started yet.
* fix(terminal): a new SSH terminal opened before sign-in waits and starts after sign-in
A terminal created while its managed SSH host is still waiting for a typed
password or passphrase got main's sign-in refusal from terminal.create and
showed it as a fatal error. Signing in later never started it.
Route that refusal into the same parked retry #32275 gave restored panes, so
the post-sign-in control-ready revival replays the create. The pane shows a
'Waiting for SSH sign-in' banner with a Sign in action instead of an error.
* fix(terminal): clear the sign-in wait as soon as sign-in finishes
Publish the sign-in reason through one setter so clearing it always reaches
the banner, and clear it when the control socket proves sign-in finished.
The Sign in button retries only after a successful connect, so a rejected
password cannot re-dial into a second prompt. Share one banner layout.
* fix(terminal): keep a sign-in-waiting spawn pending until the replayed create lands
The pane's spawn finalization (launch config registration, startup delivery)
ran against an undefined result while the create waited for SSH sign-in,
then never saw the terminal the post-sign-in replay made. Keep the original
connect pending and settle it with the replay's result, or with nothing when
the pane is closed, re-attached or a fresh connect supersedes it.
* fix(terminal): a stale replay settles only its own sign-in wait
* fix(terminal): settle a sign-in-waiting spawn at the recovery state, not per replay
Codex and Opus found recoveries that finish outside a create replay (the
stream resubscribe after the create landed, an ownership refresh) and left
the original connect pending. Settle it at the two points every recovery
passes: healthy (with the bound terminal) and idle or disposed (with nothing).
* fix(i18n): clearer Spanish sign-in wait copy
* fix(checks): stop stale-while-revalidate from flashing a stale merged PR
The synchronous fresh-return path guards on `staleMergedHeadRefetch` so a
worktree that advanced off a merged PR's head does not reuse the now-stale
merged review (added in #7277). The stale-while-revalidate fast path was
missing that same guard, so it served the stale merged card synchronously
(and kicked only a background refetch) whenever the parent-PR-checks overview
polled with `{ staleWhileRevalidate: true, currentHeadOid }`.
Result: a worktree that commits new work on top of a merged branch keeps
showing the old "Merged" card instead of going branch-only — persistently
while the background revalidation keeps failing (rate limit / gh error).
Add `!staleMergedHeadRefetch` to the SWR condition so the read revalidates
synchronously, matching the fresh-return path. Adds a regression test that
fails without the guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(review): clarify stale review revalidation comments
* fix: avoid reusing hosted review requests from a different head
* fix(checks): track concurrent review lookups by Git head
* fix(hosted-review): narrow per-HEAD request fencing to a join guard
Replace the HEAD-keyed in-flight map with the original single slot per
request key, and skip joining an in-flight lookup only when it was started
for a different known HEAD. Keep the stale-merged guard on the
stale-while-revalidate path and the merged-HEAD guards on the readers.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
* fix: back off failed GitLab authentication probes
* fix(gitlab): use short exponential backoff for failed host-discovery probes
Start at 15s and double up to the existing 5-minute negative TTL, and
clear the backoff on auth refresh (rememberGlabKnownHosts), successful
probes and forced preflight resets so recovered auth is seen promptly.
Keeps the positive host cache shape unchanged.
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(keybindings): report conflicts between terminal shortcuts and chords that fire in terminals
Actions that reach a focused terminal now share a conflict bucket, and the
Shortcuts pane judges the bindings as written in the file instead of the
map the loader already stripped.
* fix(keybindings): flag only the ignored binding and say it was ignored
The other claimant keeps its own default and never stopped working, so a
warning on its row blamed an untouched action. Adds the e2e regression that
seeds a conflicting file inside the isolated E2E home.
* test(keybindings): capture the shortcuts pane at each stage of the conflict report
* test(keybindings): assert the untouched action carries no warning
Visibility alone would still pass if a regression warned on both rows.
* fix(keybindings): narrow the ignored-binding guard and tighten its negative test
Review follow-ups:
- Only warn when the file asked for a binding and the loader took it away.
The guard now also requires the action to be absent from the active map,
so an override that survived is never labelled "was ignored".
- The negative case used view.tasks, which has no default binding, so the
conflict assertion passed without exercising the terminal bucket at all.
It now uses worktree.quickOpen and asserts the definition exists, is not
terminal-reachable, and has a non-empty default list.
* fix(keybindings): compare main-process shortcuts against every scope
- List the actions resolveWindowShortcutAction consumes in main and put them in every scope's conflict bucket, so a binding elsewhere on the same chord is reported instead of silently losing it.
- Browser bindings are compared only against the actions a focused guest forwards ahead of browser chords.
- Dictation stays out, since main lets its chord through while voice is off (the default).
- The two digit ranges may still share a modifier.
- Drop the allowInTerminal terminal-bucket rule.
- Explain overrides the loader drops on a later pass.
- Pin the list to the resolver and the guest order with tests.
* fix: report main-intercepted shortcuts in Settings without dropping saved overrides
Main-process interception now only feeds the Settings conflict report.
The loader, write path and web normalization keep main's scope-only check,
so existing keybindings.json overrides load exactly as before.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>