Pure rename, no behavior change. Callers like isCommandAvailable and
isCommandOnPath wrap this in try { ... return true } catch { return false },
collapsing "distro unreachable" and "command absent" into the same value —
a recurring bug class in this subsystem (see
docs/reference/wsl-probe-failure-semantics.md). The OrThrow suffix makes
that swallow visible at the call site instead of implicit in the function
name, so a reviewer notices when a new caller does the same collapse.
* fix(browser): apply the app-wide HTTP proxy to embedded browser sessions
The proxy setting was only ever written to `session.defaultSession`, but browser
guests run on their own `persist:orca-*` partitions. Any host reachable only via
the configured proxy failed to load in an embedded tab, landing on
`chrome-error://chromewebdata/`, while the same setting worked everywhere else.
Adds a per-session applier alongside the existing defaultSession path, keyed by a
WeakMap so one session's applied config can't suppress another's, and applies it
to every browser partition through the single installer they all pass through.
Startup awaits an explicit sweep so the first guest navigation can't race the
installer's fire-and-forget write, and a settings change re-sweeps so toggling
the proxy takes effect without a restart.
Env-var fallback and the system-proxy probe mirror the defaultSession behaviour,
so a browser partition resolves the proxy the same way the rest of the app does.
Fixes STA-4779
* fix(browser): await per-session proxy readiness
* fix(proxy): preserve loopback and authenticate
* fix(proxy): settle browser partition update races
* fix(proxy): close partition policy races
* fix(proxy): order settings and release removed sessions
* test(browser): await partition proxy readiness
* fix(proxy): cancel removed partition retries
* refactor(proxy): keep OpenCode rate limits out of scope
* fix(proxy): preserve sessionless host policy
* fix(proxy): gate requests on policy readiness
* fix(proxy): retire deleted browser sessions
* fix(proxy): close retired browser guests
* fix(proxy): retain retired session guards
* fix(proxy): retain retired partition policies
* fix(browser): retry transient proxy application failures
* fix(browser): release deleted partition installer state
* fix(proxy): retry delayed transient failures
* fix(proxy): preserve route session authority after rebase
* fix(proxy): clear retired session credentials
* fix(proxy): retire failed browser profiles
* fix(proxy): harden failed session cleanup
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
* fix(wsl): scan sessions only in running distros
* test(ai-vault): pin WSL discovery platform
* fix(wsl): suspend transcript watchers for stopped distros
* test(wsl): pin transcript scan gate platform
* fix(wsl): settle stopped transcript loading
* fix(wsl): add last-known-good fallback and backoff to running-distro discovery
listRunningWslDistrosAsync failed closed on any probe error (timeout, ENOENT,
wsl.exe hiccup), indistinguishable from "no distros running". A 2s poll
(wsl-transcript-running-observer.ts) calls it indefinitely while any WSL
transcript tab is open, so a persistently broken wsl.exe silently made every
WSL session vanish app-wide with no way to tell "discovery broken" from
"distro stopped", and re-spawned wsl.exe every 2s forever.
Extract a dedicated cache/backoff module (wsl-running-distro-cache.ts,
mirroring the sibling machinery already in wsl.ts for the full distro list)
so a probe failure falls back to the last-known-good running-distro list and
backs off further probes, while a genuine empty result (no distros running)
stays authoritative. Add a consumer-level test simulating a sustained wsl.exe
outage across a live transcript-watcher polling session, asserting the
observer keeps reporting "running" and that real wsl.exe spawns stay bounded.
* fix(build): list the new WSL cache module in the web typecheck project
config/tsconfig.tc.web.json enumerates its files explicitly, so a new
module imported by wsl.ts fails the full typecheck with TS6307 until it
is listed. pnpm tc:node passes without it, which is how this got missed.
src/main/wsl.ts(13,8): error TS6307: File 'src/main/wsl-running-distro-cache.ts'
is not listed within the file list of project 'config/tsconfig.tc.web.json'.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* test(wsl): guard probes that report failure as a negative answer
A WSL probe that cannot reach its distro returns the same value as one
that asked and got "no". Downstream nothing can tell them apart, so a
distro that was busy for a second reports no git, or no agent sessions,
until relaunch — sticky, silent, and identical to the real thing.
That has shipped three times: preflight CLI probes, the glab auth
fallback (#8941), and listRunningWslDistrosAsync failing closed with no
last-known-good while polled every 2s (PR #17072).
Scan the WSL and preflight probe modules for the shape and hold the
current set in an allowlist that only shrinks. Scoped deliberately: the
same shape appears ~850 times across src/ and is usually correct,
because for most callers a failure really does mean absent. It is only
dangerous where the answer describes a distro.
The guard cannot see the dangerous part — whether the value is later
cached or gates discovery is dataflow, not syntax. It stops a new
swallow site appearing here without someone saying why it is safe to
pin, which is the review that was missing all three times.
* test(wsl): make ratchet failures actionable
A red build must say what to do. Name the offending files, say the
allowlist is where a safe case goes, and — for a stale entry — say the
change is fine and the list just needs to shrink.
* docs(wsl): track the probe failure-semantics reference
docs/** is gitignored with an explicit allowlist, so the reference the
ratchet points contributors to was silently left out of the branch. A
guard whose error message cites a doc that is not in the repo is worse
than no doc.
* test(wsl): catch a swallow whose reason trails the return
The guard only tolerated comments before `return`, so
`return false // ...` slipped past — including the exact snippet the
doc and the test's own docstring use as the canonical example. The doc
asks authors to write down why a swallow is safe, and the natural place
for that sentence is trailing the return, so following the guidance
defeated the guard.
Verified against both shapes: trailing comment and comment on the line
after.
* fix(cli): name PowerShell when it strips quotes from JSON flags
Windows PowerShell 5.1 does not escape inner quotes when building a native
command line, so `--options '["a","b"]'` reaches orca.exe as `--options [a,b]`.
The value is correct when printed and damaged by the time argv is parsed, so the
resulting "invalid JSON" error blamed the user's input rather than the shell.
#16743 recovered this for `--deps`, which is safe only because generated task IDs
have a fixed 12-hex grammar. The same mangling hits `--options`, `--payload` and
`--result`, and those are NOT safely recoverable: `["1","2"]` and `[1,2]` arrive
at argv identically, so a general repair would silently turn strings into numbers.
Detect instead. `getOptionalJsonFlag` rejects the damaged shape up front with an
error that names the shell and shows the workaround. It fires only when the value
is bracketed, quote-free, fails JSON.parse, AND consists entirely of bare tokens
that quoting would rescue, so valid JSON is untouched.
Also share the generated-id contract: `task-deps-flag` hardcoded
/^task_[0-9a-f]{12}$/i, which silently diverges if `generateId`'s byte count
changes. It now calls `isGeneratedId`, with a test pinning the two together.
Verified on a Windows host. Measured argv, which the new test pins as a fixture:
PS_VALUE=["task_b2a580db74d8","task_c3b691ec85e9"]
ARGV=["--deps","[task_b2a580db74d8,task_c3b691ec85e9]"]
Before: Invalid --options: must be a JSON array of strings
After: --options arrived as [a,b], which is not valid JSON.
Windows PowerShell 5.1 strips the inner quotes ...
* fix(cli): scope JSON-flag detection to genuinely JSON flags
Review found the detector wired to two flags that are not JSON:
- `orchestration ask --options` is documented `<csv>` and the runtime splits it
on commas, so `--options [a,b]` was a legitimate value being rejected.
- `task-update --result` is stored verbatim and reused as dispatch failure text;
existing tests pass free text, so a bracketed `[ok]` was being rejected.
Both revert to `getOptionalStringFlag`. Only `gate-create --options`
(`<json_array>`) and `send --payload` (`<json>`) are JSON-parsed and keep it.
Three further review fixes:
- Objects now require a `key:value` pair per entry. `{a,b}` and `{a:b,c}` were
reported as quote-stripped although quoting them cannot produce valid JSON.
- The raw value is no longer echoed. A `--payload` can carry secrets and this
message reaches `--json` output; the flag name and guidance are enough.
- The message hedges the shell attribution. Detection inspects only the value's
shape, so it also fires when a macOS/Linux user forgets to quote, where
PowerShell is not involved.
Verified against a Windows host, all six cases: both JSON flags fire on the
mangled shape and pass valid JSON through to the runtime; both non-JSON flags
now reach the runtime again; and the secret in `{token:hunter2}` appears zero
times in the error output.
* fix(linear): flag partially applied status and label filters (STA-5983)
Since #16879 one status/label row expands to an id per team, and the renderer
bounds that list to the 100-id transport cap. Any surviving id kept the row
fully checked, so the picker claimed coverage the filter never had; show how
many of the row's per-team ids are actually applied.
* fix(linear): keep every picked status row inside the transport cap
The cap sliced a lexicographically sorted id list, so a whole picked row could lose
every id — reverting to unchecked with no notice, and dropping out of the coverage
denominator that was supposed to explain it. Spread the cap across the picked rows,
and carry the partial-coverage signal to the section menu and the pill, which are
what the user reads once the detail panel is closed.
* fix(linear): stop the status filter claiming coverage it cannot apply
More picked rows than the transport id cap cannot all be represented, and
MultiSelectList.toggle appends the clicked key last — so the starved row was
always the row the user had just clicked: it stayed unchecked, no notice fired,
and coverage still reported a full 100 of 100. Coverage now takes the cap and
reports a spent id budget as its own shortfall, so the picker says how much it
is really carrying instead of claiming teams it never covered.
Capping also bucketed by the click order the picker hands it, so the same
visible selection could resolve to different ids between renders; it now buckets
in metadata order, with ids from unloaded teams sorted after. boundLinear-
IssueAttributeFilter stays the last word on the transport bound.
The pill's `partial` marker moves from a bare title attribute to the Tooltip
primitive, which keyboard and screen-reader users can actually reach.
Pill labels and facet clearing move to their own module so sections stays under
the max-lines cap.
* test(linear): assert coverage at the cap it actually caps to
The exactly-on-the-cap test capped at max=4 but asserted non-partial at
max=5, so it never covered its own subject. Pin both: at the cap coverage
warns (a starved row leaves no trace in the ids), below it stays quiet.
* Consolidate the renderer now-clock and drop the epoch setState round-trip
Follow-up cleanup to #17337.
`src/renderer/src/hooks/use-now.ts` duplicated the shared clock that already
lived at `components/dashboard/useNow.ts`, with a per-instance `setInterval`
and no visibility gating — the exact pattern that file's own comment warns
against. Keep the shared `useSyncExternalStore` implementation, move it to the
`hooks/` home the duplicate had taken, and give it the `enabled` flag that was
the duplicate's only real addition. A disabled caller no longer holds the
shared interval open or re-renders on its ticks.
Gate the two 1 Hz callers on the state that can actually consume them: the
checks-panel empty content only reads the clock for a GitHub auto-retry or
retry-disabled window, and the diff notes menu only for an open request already
addressed to its worktree. Both previously ticked for their whole lifetime —
the empty content re-rendered the create composer once a second while the user
typed in it.
Replace the `setState`-in-`useEffect` epoch clocks with a sample keyed on
`agentStatusEpoch`. The effect ran a render late, so the freshness-scheduler
bump — whose whole purpose is to expire an entry on the stale boundary — first
painted a frame that still read the pre-expiry timestamp, then corrected it.
Sampling during render keeps the value deterministic per epoch and every
consumer of one epoch agreeing on the boundary.
Also: `isPanelVisible` never gates the checks-panel clock (ChecksPanel is
unmounted, not hidden), `setPrRefreshStateNow` was returned but never read,
`panelContextKey` was an unused dep on the expiry effect already keyed by
`prCacheKey`, `useResetCountdownClock` kept a dead alias, and `ProviderPanel`
derived its window sections twice per render.
* Fix the open-request TTL and the epoch clock's captured Date.now
Review findings on the previous commit.
The diff notes menu's 5s TTL stopped working. The shared clock's snapshot is
frozen while nobody at that cadence is subscribed, and `useSyncExternalStore`
subscribes in a passive effect — which flushes child-first, so NotesSendMenu's
open effect ran before the clock could catch up. With both 1 Hz callers now
narrowly gated, nothing holds that cadence, so an open request that was never
consumed could reopen the menu arbitrarily later.
The TTL is a deadline, not a drifting label, so enforce it on the commit that
acts on the request: DiffNotesSendMenu passes `openRequestExpiresAt` and
NotesSendMenu checks it against `Date.now()` in the effect that opens the menu.
Exact, and it removes the 1 Hz clock from that path entirely.
`createAgentStatusEpochClock`'s `readNow = Date.now` default bound the native
function when the module-load singleton was created, so a suite's fake timers
never applied to it. Call through instead. Also add a reset seam: store resets
rewind `agentStatusEpoch` to 0, and without rewinding the sample the next render
at epoch 0 reuses the previous test's timestamp.
Both regressions have tests that fail without the fix. Also corrects the
disabled-caller contract on `useNow` — the snapshot is frozen, not merely
bounded by an enabled caller — and notes on the three memos that they stay keyed
on the epoch because two bumps in one millisecond share a sample.
* perf(preflight): cache WSL CLI probes per distro
A preflight check against a WSL target skipped the cache entirely
(`cacheable = !wslTarget`), so every caller re-spawned up to five
`wsl.exe` probes — git/gh/glab detection plus gh/glab auth, two of them
through login shells — and woke an idle distro each time. Repeated
Landing mounts, pane switches and per-worktree restore each paid the
full set.
Cache per distro so one distro's toolchain never answers for another,
and join concurrent callers onto one probe set instead of letting each
run its own.
The entry expires rather than living for the session like the local
cache does: `isCommandAvailable` collapses every failure into
`installed: false`, so an unreachable distro is indistinguishable from
one with no tooling. Pinning that would report "git not installed"
until relaunch, where the uncached code self-healed. Expiring keeps the
burst collapsed and still lets a transient failure recover.
Propagating unreachable-vs-absent out of the probe layer would allow a
longer-lived entry, but that reaches well past WSL and belongs in its
own change.
* fix(preflight): stop a superseded probe caching its stale result
Review caught two races the first version had.
A forced refresh runs alongside a slower probe already in flight. Both
wrote the cache unconditionally on settle, so the older one landing last
replaced the newer answer — a Re-check could silently return the status
it was asked to replace, for the whole TTL.
The same write also repopulated a cache that `_resetPreflightCache` had
just cleared, which jira/linear call on credential changes: the probe
already out would settle afterwards and restore what was invalidated.
Tag each run and only let it write while it is still the newest for its
key, with an epoch doing the same across a reset. A superseded run still
returns its own answer to its own caller; it just stops becoming the
cached one.
Both tests fail without the guard.
* fix(remote): focus host-delegated split panes
Return the authoritative leaf identity from terminal.split, record viewer-local focus intent behind the captured pairing revision, and replay the mirrored layout before focusing the exact pane. Preserve old-host fallback and prevent delayed split responses from stealing focus after the viewer moves away.
Add deterministic runtime, renderer, concurrency, compatibility, and headed paired-Electron coverage for Cmd+D, header splits, and immediate PTY input routing.
Fixes#16510
* fix(remote): preserve split focus across tab groups
Resolve the initiating source tab and leaf from the remote PTY, while keeping the viewer's current focus as a separate anti-steal baseline. This lets context-menu/header splits from non-focused group tabs focus their result without allowing delayed responses to override a later navigation.
* test(remote): drive split focus with key events
* test(remote): use the platform split shortcut
* fix(remote): fence concurrent split focus intent
* fix(remote): harden split focus ordering
* fix(remote): preserve split focus after runtime refactor
* fix(remote): fence stale split focus gestures
* test(remote): keep split focus regression within line budget
* Enable React purity and static component lints
* fix lint follow-up clock activation and eligibility expiry
* test render status bar provider panel with hooks
* fix checks clock activation before paint
* fix React type import in git history files
Reuse direct SSH browser-route eligibility and route owner-pinned terminal links through the workspace createBrowserTab path. Keep the popover and modifier hints aligned with the eligible destination.
Preserve printed 0.0.0.0 and localhost URLs; the existing SSH SOCKS boundary normalizes wildcard listeners to remote loopback immediately before connect.
Keep NativeChatView focused on mode routing and session-gate ownership by moving the resolved bridge UI into its own component. This behavior-preserving split clears the pre-existing max-lines baseline without disabling the lint rule.
Take the high-value desktop and mobile upgrades that fix crashes, jank,
or security holes. Leave Electron 44, Lucide 1, Reanimated 4.6, Expo
56/57, and xterm betas for later.
Desktop: electron 43.4.1, @tanstack/react-virtual 3.14.10, mermaid
11.17.2, ws 8.21.3, react 19.2.8, pdfjs-dist 6.3.289, vitest 4.1.11,
happy-dom 20.11.8.
Mobile: Expo SDK 55 patch train, react-native 0.83.10 (IME patch
ported), reanimated 4.3.4, webview 13.16.2 (thread-safe decision
manager; restore WebView generic default so TS 6 does not collapse
props to never).
Electron 43.4 dropped marginType from PrintToPDFMargins; CDP print
mapping now supplies the four sides only.