Commit Graph
7736 Commits
Author SHA1 Message Date
Jinjing bc17d4e57f fix(win32): extend IDE launcher console suppression to direct paths
Support IDE paths stored directly in settings (e.g., idea.exe,
webstorm.cmd). Detect console idea.exe stubs alongside batch shims
for upgrade to GUI *64.exe. Fix start command title escaping: use
empty string instead of '""' to prevent libuv re-quoting.
2026-08-01 13:07:19 -07:00
Jinjing 74a8118a4d fix(win32): suppress Command Prompt window on IDE launches
Prevent lingering Command Prompt windows when launching JetBrains IDEs
on Windows. Use `start "" /B cmd /d /c` so the nested shell exits with
the batch script, but only for JetBrains shims—VS Code and Cursor keep
the waiting form because `start` re-parses arguments and breaks remote
paths with spaces. Prefer colocated `*64.exe` executables beside the
resolved `.cmd` shim over PATH lookups to avoid stale installations.
2026-08-01 13:05:58 -07:00
Jinjing 4ff8527920 fix(win32): suppress Command Prompt window on IDE launches
- Prefer JetBrains GUI executables (`*64.exe`) over `.cmd` shims to avoid
  console allocation (STA-3040).
- Use `start "" /B` when launching GUI apps via batch scripts; shims chain
  through console helpers that allocate a visible prompt even with
  `windowsHide`. `start /B` returns immediately, preventing the lingering window.
2026-08-01 01:49:04 -07:00
OrcaWinandNeil c8a22ad0a6 fix(terminal): make snapshot capability lookup async (#11881)
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
2026-07-31 23:38:26 -07:00
NeilandBrennan Benson 6e7ceafd07 perf(mobile): avoid unchanged worktree catalog payloads (#11735)
* perf(mobile): avoid unchanged worktree catalog payloads

* fix(mobile): isolate catalog snapshots by limit

* review: reassert host truth on unchanged polls; content-address snapshots

Client — the `changed` gate meant an unchanged poll skipped setWorktrees /
setLastKnownWorktrees / setCachedWorktrees, so optimistic local edits
(togglePin, handleDeleteWorktree's failure re-add) and the #8498 cache guard
were no longer repaired while the host catalog was stable. The gate bought
nothing: setCachedWorktrees is an in-memory Map write and areWorktreeListsEqual
already ran every poll, so the steady state still short-circuits on array
identity. All wire savings are unaffected. admit() now just returns the
confirmed rows and HostScreen applies them exactly as it did pre-PR.

Also on the client:
- a stale response from a superseded client/host no longer clears the token the
  current client/host just established
- discriminate on `worktrees` rather than on `'unchanged' in response`, so a
  future catalog field named `unchanged` can't reclassify a full response
- useRef over useMemo for the snapshot client; React may discard memoized values
- hoist WORKTREE_PS_FULL_LIMIT so the truncates-at-200 rationale travels with it

Host — replace the per-limit snapshot cache with a content-addressed id (ETag
semantics). Ownership lives in the id, so concurrent clients, differing limits,
and runtime restarts are correct by construction; this drops the LRU, the
eviction policy, the per-runtime WeakMap, and the retention of up to 8 full
catalogs. The remaining cache is a pure memo: because ids derive from content,
dropping or thrashing it costs CPU and nothing else. Keeping the memo also
keeps the measured steady-state cost — hashing every poll instead measured
2.24ms vs 0.75ms for the compare on a 310KB catalog.

Verified: mobile 2784 passed / 3 skipped, src/main/runtime/rpc 1064 passed,
node + mobile typechecks, oxlint, oxfmt, max-lines ratchet.

* fix(runtime): isolate catalog snapshot memo

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-31 23:12:13 -07:00
Brennan Benson 4c03cdff72 fix(mobile): mount host before opening tasks (#11853) 2026-07-31 20:46:51 -07:00
Brennan Benson ed00ab0f34 fix(ssh): restore relay ownership after app restart (#11860) 2026-07-31 20:45:52 -07:00
Jinwoo HongandOrcaWin c09a2ee251 fix(mobile): open resume workspace route reliably (#11876)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 20:42:05 -07:00
Brennan Benson 402e49203d fix(codex): keep persistent panes logged in after home routing (#11720) 2026-07-31 18:33:26 -07:00
072133fb27 fix(preflight): route landing banner through the runtime-aware preflight slice (#11390)
* fix(preflight): route landing banner through the runtime-aware preflight slice

Landing called window.api.preflight.check directly, which always probes the
local client. The preflight slice is the only caller that consults
getActiveRuntimeTarget and forwards to preflight.check on the active runtime
environment, so while connected to a remote runtime the landing banner
reported the client machine's git/gh state instead of the server's.

Delegate to refreshPreflightStatus and derive the issue list from
state.preflightStatus. This also drops Landing's duplicate probe: the slice
dedupes concurrent and forced checks, so the mount/focus/poll paths now share
one in-flight request with the rest of the app.

* fix(preflight): refresh landing status across runtime sessions

* test(preflight): cover paired runtime session races

* test: make landing preflight oracle behavioral

* fix(preflight): scope runtime session invalidation

* test(preflight): cover headed runtime switching

* test(preflight): isolate runtime status toast

---------

Co-authored-by: Marty <marty@localhost>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 18:01:24 -07:00
OrcaWin 33ad64b1c8 fix(runtime): bound persisted graph hydration (#11832) 2026-07-31 17:56:40 -07:00
Brennan Benson 377b580bab Restyle voice dictation feature tip (#11842)
* Restyle voice dictation feature tip

* Remove workspace name from dictation preview

* Refine voice dictation tip instructions

* Clarify voice dictation setup action

* Fix voice dictation tip keyboard behavior

* Use neutral voice dictation demo prompt
2026-07-31 17:51:03 -07:00
Neil 5738d61fe0 fix(workspaces): guarantee Korean flag kr shortcode (#11858) 2026-07-31 17:33:35 -07:00
Brennan Benson a53c5d3fb0 fix(mobile): stop serving a pre-write host-list snapshot to loads issued after the write (#11458)
* fix(mobile): stop serving a pre-write host-list snapshot to loads issued after the write

removeHost/persistHost await hostListMutation, but the in-flight loadHosts()
de-dupe handed back a pass that started BEFORE the write committed, so a load
issued after removal repainted the removed host card (#8791). Every durable
write now drops the shared pass via host-list-load-sharing.ts so the next
caller reads fresh; concurrent loads with no write between them still share
one Keychain pass.

Also extracts the host action sheet into host-list-action-sheet-actions.ts to
pin closeBeforePress on Edit host + Remove (the freeze half of #8791, already
fixed by #8536).

* fix(mobile): invalidate host loads after token writes

* fix(mobile): protect host token cache from stale reads
2026-07-31 17:26:42 -07:00
fsdwen 1a0a1ce4d0 fix(i18n): correct mistranslation of Grace/grace period in Chinese (#11505) (#11507) 2026-07-31 17:18:36 -07:00
Neil e79304ccca feat(workspaces): add Korean flag shortcode (#11845) 2026-07-31 17:16:49 -07:00
Neil 3c05e03c6f Revert "feat(workspaces): add Slack emoji aliases (#11837)" (#11843)
This reverts commit adc56a71f2.
2026-07-31 17:09:41 -07:00
Neil adc56a71f2 feat(workspaces): add Slack emoji aliases (#11837) 2026-07-31 16:39:38 -07:00
Brennan Benson 9bf05b0a9c Prevent Agent sleep while orchestration dispatch is active (#11808)
* fix(agent-sleep): keep active dispatch workers awake

* fix(agent-sleep): harden background work detection
2026-07-31 16:35:33 -07:00
Neil 676964b099 ci: run only changed e2e specs on pull requests (#11834) 2026-07-31 16:25:13 -07:00
Rod BoevandOrcaWin f56e6ade80 fix(ssh): recover orphaned relay install locks (#9828) (#10207)
* fix(ssh): recover orphaned relay install locks (#9828)

* test(ssh): split staged upload relay specs (#9828)

* fix(ssh): verify staged relay upload namespace

* fix(ssh): bound stale relay stage cleanup

* fix(ssh): complete bounded stage recovery

* fix(ssh): generate valid PowerShell stage scripts

* fix(ssh): make staged upload cancellation safe

* fix(ssh): fence staged relay recovery

* test(ssh): align deploy timeout oracle

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 16:17:37 -07:00
OrcaWin 139f756064 Add recursive sleep actions for workspace descendants (#11810) 2026-07-31 16:15:20 -07:00
Brennan Benson e4937956b3 Improve disconnected server recovery copy (#11827) 2026-07-31 16:03:59 -07:00
475f63ea1b fix(remote): scope renderer throttling to paired terminal publication (#11581)
* fix(remote): unthrottle host renderer while serving a paired client

A paired desktop host left in the background could not open or close
agent sessions for its remote/relay client: the action stalled and
eventually failed with the host-side "Timed out waiting for terminal
surface after creation" (10s) error, while an already-live terminal's
keystrokes stayed fast.

Root cause: creating/closing a session routes through the host
renderer's setTimeout-coalesced graph sync to publish the terminal
surface, but the host window runs with Electron background throttling
(the hidden-window default, reaffirmed on macOS). When the window is
backgrounded/occluded, those renderer timers are throttled to a crawl
and the surface publication misses the 10s deadline. Live keystrokes are
unaffected because PTY I/O flows through the main process, never the
renderer.

Keep the authoritative renderer unthrottled while at least one remote
client is connected and restore the throttled power-saving default once
the last one disconnects. Connect/disconnect are driven from the shared
MobileSocketWiring onReady/onClose, so both direct-WS and cloud-relay
clients are covered; headless serve has no window and is a safe no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor(remote): tidy renderer-throttle comment and test per review

Address automated review nits on #11581:
- Trim the module-level rationale comment to the non-obvious contract,
  matching the repo's concise-comment guideline.
- Drop the dead `detachedThrottle` variable from the reapply test; the
  detached-target scenario is already covered by the lazy-resolution
  test, so the case now asserts only what it exercises.

No behavior change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(remote): scope paired terminal publication throttling

Keep headed paired terminal creation and close renderer-owned so host inventory, input routing, ACK recovery, and cleanup retain the established lifecycle. Hold a reference-counted background-throttle lease only while the renderer publishes a paired operation, and epoch-fence async resolution so renderer reloads reject before any request or PTY spawn. Preserve headless main ownership and prevent paired clients from falling back to a local terminal.

* test(e2e): verify minimized host terminal repaint

* fix(remote): preserve paired terminal inventory through graph gaps

---------

Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 16:01:44 -07:00
Brennan Benson fcb23e19cd fix(sidebar): stabilize cross-host project grouping (#11805)
* fix(sidebar): stabilize cross-host project grouping

* fix(runtime): suppress outage toast on disconnect

* fix(runtime): dismiss intentional outage toast

* fix(runtime): keep offline retry toast visible
2026-07-31 15:38:08 -07:00
Brennan Benson c6d2180417 fix(mobile): keep source-control layout steady while Create PR eligibility loads (#11467)
* fix(mobile): keep source-control layout steady while Create PR eligibility loads

The Create PR entry unmounted until the first hostedReview.getCreationEligibility
answer arrived, so on a cold open the changed-files list painted first and then
shifted down 54pt (createPrBlock marginTop 12 + createPrButton minHeight 42)
when the button appeared — while the user was already tapping (#8411).

- buildMobileCreatePrAction: cold loading now reserves the row with a disabled
  placeholder instead of unmounting it.
- useMobileHostedReviewEligibility: a fetch-imminent idle frame renders as an
  in-flight load, so the reservation is present on the first painted frame.
- New per-worktree+branch memory of the last resolved eligibility seeds cold
  loads, so branches whose answer is hidden (existing review, unsupported
  provider) do not get a placeholder that collapses on every reopen.

Fixes #8411

* fix(mobile): harden source-control layout reservation

* fix(mobile): keep review status row footprint fixed

* fix(mobile): derive eligibility state from keyed snapshots
2026-07-31 15:35:56 -07:00
github-actions[bot] 5e258a9447 release: v1.4.163 v1.4.163 2026-07-31 22:15:11 +00:00
Brennan Benson 4205d5f31f Refine macOS access prompt guidance (#11822)
* Refine macOS access prompt guidance

* Capitalize MacOS in access prompt

* Use official macOS styling in prompt
2026-07-31 15:13:41 -07:00
Brennan Benson 751b6b119b fix(scm): keep git-status pacing across scheduler rebuilds (#11820)
* fix(scm): keep git-status pacing across scheduler rebuilds

* fix(scm): order shared refresh pacing updates
2026-07-31 15:13:04 -07:00
Brennan Benson 4d044c47dc Revert "Clarify macOS access prompt guidance (#11807)" (#11821)
This reverts commit 7d24dad48a.
2026-07-31 14:43:30 -07:00
NeilandBrennan Benson 9f30a780f5 fix(codex): prevent transient managed-auth onboarding (#11731)
* fix(codex): gate terminal spawn on managed auth readiness

* fix(codex): recover unavailable managed auth safely

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
2026-07-31 14:40:46 -07:00
Brennan Benson 7d24dad48a Clarify macOS access prompt guidance (#11807)
* Clarify macOS access prompt guidance

* Make settings target highlight visible
2026-07-31 14:38:27 -07:00
Neil 90963f8ee3 [P1] fix(routing): keep the paired-HUB owner for the active workspace selection (#11818)
* [P1] fix(routing): keep the paired-HUB owner for the active workspace selection

#11346 made `activeWorkspaceExecutionHostId` authoritative for the active
workspace, but the short-circuits that read it never see the owner record, so a
project reached through a paired runtime HUB (`hostId: ssh:<target>` plus
`runtimeOwnerEnvironmentId: <hub>`) loses its transport and its repo:

- `resolveWorktreeOperationRouteResult` returned `runtimeEnvironmentId: null`
  for any non-`runtime:` selection, bypassing the HUB recovery in
  `resolveExactWorktreeRoute` — every owner-routed terminal/git/filesystem
  operation on the active workspace lost the HUB that proxies the SSH target.
- `selectRepoByIdForActiveWorkspace` filtered `state.repos` by the selected host
  with no fallback, but `withRepoHostOwnership` deliberately keeps the SSH host
  on the worktree while the repo row stays HUB-owned — so the active repo read
  as `null` and `useGitStatusPolling` stopped polling the whole workspace.

Route resolution now recovers the HUB owner from the owner rows on the selected
host, and drops it only when rival HUBs project the same host. The repo selector
opens exactly one hole in #11346's fail-closed rule — an `ssh:` selection over a
`runtime:` repo row — and every other host mismatch still returns `null`.

`worktree-owner-route.ts` is a pure extraction of the existing owner-route
helpers; no behavior moved with it.

* fix(routing): fail ambiguous paired-HUB repo selection closed
2026-07-31 14:38:08 -07:00
NeilandOrca cd2b62ed14 feat(updater): name hourly releases by version, build number, time, and sha (#11817)
* feat(updater): name hourly releases by version, build number, time, and sha

Hourly releases were titled with their raw tag
(`v1.4.163-hourly.202607312054`), which reads as one opaque digit run and
does not say which commit it came from.

Title them `1.4.163 • 01 • 07-31 13:54 • e698241` instead, and show that
same string in the in-app build picker by having the picker render the
release's stored name rather than deriving its own label. Composing it in
one place means the two surfaces cannot drift.

The build number is monotonic across the channel. It is read as the
highest number already in use rather than as a count of releases: the
prune step trims to 72, so a count would roll backwards after three days
and reissue numbers. Drafts count toward it — unlike in the freshness
check, which asks whether a commit shipped, this asks whether a number is
free, and a stranded draft still holds one.

Times are Pacific while the tag's stamp stays UTC. The stamp is a sort
key and a local one would repeat an hour at every DST fall-back, making
two distinct builds compare equal; the title is only ever read.

* fix(updater): fail the hourly build when the release name is missing

The workflow checks out `ref: main`, but a workflow_dispatch runs the
workflow file from whatever branch was dispatched. A branch that edits
this step while main still carries the old script produces an empty name
and an untitled release — silent, and only visible once someone opens the
releases page. Verified by hitting exactly that on run 30665586904.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:29:20 -07:00
NeilandOrca bd490b14aa [P2] fix(terminal): always close the bracketed-paste frame and hold the pty lock through submit (#11816)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:05:07 -07:00
NeilandOrca a51248e42a [P2] fix(mobile): put the PR sidebar and branch chip on the shared check classifier (#11815)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:03:35 -07:00
NeilandOrca ea40c87315 [P2] fix(skills): keep the npx preflight in the forced-PowerShell setup terminal on Windows (#11814)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:03:26 -07:00
NeilandOrca 81fee4d6a4 [P2] fix(checks): give the PR page and work-item dialog the shared check-count labels (#11813)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:03:03 -07:00
NeilandOrca b998f7b13e [P2] fix(updater): route hourly release-notes links to the hourly repo and let the disabled channel tooltip open (#11812)
Co-authored-by: Orca <help@stably.ai>
2026-07-31 14:02:30 -07:00
Brennan Benson e698241aab fix(mobile): remember custom pairing addresses (#11741)
* fix(mobile): remember custom pairing address

* fix(mobile): stabilize custom pairing address sync

* fix(mobile): update pairing refresh refs after commit

* feat(mobile): manage saved custom pairing addresses

* fix(mobile): harden custom address selection
2026-07-31 13:03:11 -07:00
129d8b32bb fix(codex): trust extended-length resume paths (#10337)
* fix(codex): trust extended-length resume paths

* test(codex): cover both sides of extended-length resume folding

The provenance fix folds the extended drive spelling on the rollout path and
the trusted sessions root, and runs per entry in the legacy id scan. Only the
rollout-path side had coverage, so pin the rest:

- normal-form rollout under an extended-length trusted home (root side)
- compressed .zst sibling derived from an extended-length persisted path,
  where a folded comparison copy could leak into the returned path
- legacy id-scan entries: extended-length accepted, device namespace refused

All three fail with the production change reverted.

* docs(codex): state accurately what the resume path fold rejects

The helper's comment called the rejected spellings "arbitrary device
namespaces", but \?\UNC\ is the extended-length form of \server\share\ —
a network share, not a device. Reading it as a security decision hides that
UNC is simply not folded yet, so say what is actually excluded.

Also trim the two new test comments to one line to match the file.

* fix(codex): reject rollout alternate data streams

* fix(codex): preserve resume provenance guard

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 11:59:05 -07:00
Rod BoevandOrcaWin 271776f233 fix(codex): keep shell-profile-only Windows CODEX_HOME on the managed lane (#10221)
* fix(codex): keep shell-profile-only Windows CODEX_HOME on the managed lane

* test(codex): harden platform probe regression coverage

* test(codex): isolate Windows home ownership coverage

* fix(codex): preserve managed home in constrained PowerShell

* fix(powershell): continue constrained startup commands

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-07-31 11:58:46 -07:00
Brennan Benson 5d88c1093f docs(i18n): adopt gettext PO as the canonical translation source (#11478)
* docs(i18n): adopt gettext PO as the canonical translation source

Records the revised translation-source architecture decision: gettext PO
replaces the original constrained XLIFF 2.0 profile after tooling,
contributor-workflow, and repository-evidence review. Documents the
read-only compiler / post-merge reconciler split, the four-state entry
taxonomy, message ID and placeholder policy, mobile and plugin-language-pack
scope, and the field-by-field PR B acceptance gate. Links the decision from
the localization audit and corrects its allowlist description.

* docs(i18n): add mobile dual-projection and bridge-ID constraints

From PR 11446 owner review: mobile needs two deterministic projections
(i18next JSON + pre-JS native metadata with a documented native fallback
rule and the zh/zh-Hans locale-ID mapping); the hash-ID grandfather clause
is date-scoped to the desktop catalog so freshly minted bridge IDs must be
renamed in a dedicated adjacent change; OS permission prompts and native
app metadata join the human-review list; imported bridge provenance must
be classified in PR C.
2026-07-31 11:46:25 -07:00
github-actions[bot] b0c5bb5586 release: v1.4.163-rc.3 v1.4.163-rc.3 2026-07-31 18:28:35 +00:00
Brennan Benson f3f5a928f6 Fix duplicate terminal renderers after layout restore (#11726)
* Fix duplicate terminal PTY layout ownership

* Fix rootless duplicate PTY replay

* Normalize mirrored terminal PTY ownership

* Handle repeated terminal layout leaf ownership

* Preserve terminal ownership through duplicate layout repair

* Repair dangling PTY layout selection

* Preserve terminal ownership repair metadata

* Preserve rootless pending terminal focus

* Bound duplicate terminal layout pruning

* Preserve agent authority during layout repair

* Fix hydrated terminal pane authority repair
2026-07-31 11:25:30 -07:00
Jinjing 1c8908b791 Fix orchestration gate authorization to scope by Run binding (#11802)
* fix(orchestration): gate methods route calls to the caller's Run with `f

Gates are Run-scoped state; every gate command now resolves the caller's active Run
(via pane binding or explicit --from flag) and authorizes within that Run's scope.
Settled adopted work no longer requires --takeover-legacy, and the legacy coordinator
fence respects both binding-based and attestation-based proof of authority.

* fix(orchestration): gate methods route calls to the caller's Run with at

Gate and run methods now verify that declared terminal handles match the caller's
attested identity, preventing spoofing of other coordinators. Extracted shared
`resolveRunScope` to enforce one authorization rule across all orchestration
mutations. Added comprehensive regression tests for #11745.
2026-07-31 10:56:20 -07:00
NeilandOrca 79251d7a98 [P2] fix(release,settings): restore signing preflight portability, bootstrap diagnostics, and skill re-check (#11692)
* fix(release): restore the SignPath composite action when cutting from an older ref

Co-authored-by: Orca <help@stably.ai>

* fix(startup): record a durable diagnostic before the bootstrap fatal-exit guard exits

Co-authored-by: Orca <help@stably.ai>

* fix(settings): make agent-skill Re-check rescan skill freshness

Co-authored-by: Orca <help@stably.ai>

* fix(startup): keep the bootstrap fatal diagnostic when the log override is unwritable

Create the parent directory an overridden ORCA_BOOTSTRAP_FATAL_LOG names and fall
back to the default location when that path still cannot be opened, so a missing
parent no longer costs the only account of the failure. Also pins the Re-check
freshness rescan to the completed install scan rather than the click.

Co-authored-by: Orca <help@stably.ai>

* refactor(settings): move the post-recheck surface sync out of the panel

Co-authored-by: Orca <help@stably.ai>

* fix(startup): retain diagnostics without node fs

* fix(skills): keep freshness scoped to the local runtime

* fix(settings): register freshness status translations

* fix(settings): scope and sequence skill freshness refreshes

* fix(settings): refresh freshness across runtime transitions

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 06:08:26 -07:00
Neil 886fa7b438 [P0] fix(relay): stop the retired exit record from double-broadcasting pty.exit (#11660)
* fix(relay): stop the retired exit record from double-broadcasting pty.exit

Retiring the publication record at cancel or grace-expiry time routed the pending
exit through pty-handler's unconditional broadcast, so subscribers that already
received the exit from the record's legacy projection got a second copy (which
fans out to a whole-provider teardown on the consumer side).

- Track the legacy exit projection in a RelayPtySourceLegacyExitIndex keyed by
  pty incarnation so it outlives the delivery record; the handler's fallback now
  re-targets only source-owner clients when subscribers already have the exit.
- Cover the previously untested defense layers: B2 same-client re-attach healing,
  B3 deferred retire-on-append-failure (retire plus the microtask deferral), and
  publishPendingExit's `accepts(id) &&` sub-guard.
- Harden layer D's capacity fan-out (it sits outside the catch it depends on),
  route cancelExact's bare-timer probe through snapshotIfKnown, and keep
  appendDenied counting cancel-induced append rejections.

* test(relay): pin the retired-record exit dedup and stop its index leaking

The legacy-exit dedup layer shipped with zero coverage: deleting pty-handler's
`publishExitAfterRetire` wiring left the whole relay suite green, because the
send scheduler's partial-progress branch keeps a projected record alive so B1
handles the common case and the fallback is never reached.

- Cover `RelayPtySourceLegacyExitIndex` directly, pin pty-handler's fallback
  (a retired record re-targets its own exit instead of broadcasting a duplicate
  to subscribers that already have it, and still broadcasts when nothing was
  projected), and drive the reachable retire path: B2's re-attach retires a
  record whose projection only the index remembers.
- Forget the index entry when `exitPublicationSettled` prunes a healthy exit.
  `remember()` only re-ran from the exit path, which B0 short-circuits after a
  settled exit, so every source-mode PTY exit leaked one Map row for the
  daemon's lifetime and would re-publish on any later fallback for that id.

* fix(relay): contain retired exit publication faults

* fix(relay): retain projected exits across owner faults

* fix(relay): retire faulted exit deliveries
2026-07-31 05:44:53 -07:00
NeilandOrca 651f707ce0 [P1] fix(mobile): restore pairing self-heal and recover a wedged handshake (#11690)
* fix(mobile): restore pairing self-heal and recover a wedged handshake

readPairingKeychainItem threw when an Android presence record pointed at a
SecureStore entry that read back null. Android reports absent and undecryptable
identically, so the keystore fault the presence record was added to survive
latched every caller out of its own orphan cleanup: the pairing journal store
never reached its null-secret branch, stale winner-stamped metadata survived,
and every later QR scan failed with "mobile relay pairing recovery pending".
Report absent instead and drop the stale presence claim, still without falling
back to the superseded older generation.

The handshake-timeout path closed the socket with no handleSocketClosed
fallback, unlike the connect-timeout and activity-probe paths. When React
Native omits onclose for a wedged transport the client stayed in 'handshaking'
forever with no reconnect armed.

* fix(mobile): keep the presence pin when a recorded keychain item reads null

Clearing the presence record on the self-heal removed the only thing that
stops readPairingKeychainItem's generation walk, so the next read fell back to
the superseded value under an older generation -- exactly what #11430's
presence record exists to prevent, and reachable for host device tokens and
relay resume bundles after an Android encrypt rotation. Return null and leave
the record in place; the null return alone unlatches every caller's orphan
cleanup, and delete/re-pair already clear or re-stamp the record.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): date synthesized socket closes in transport diagnostics

Move the log-only close clocks behind handleSocketClosed's stale guard so a
synthesized close records them and a late onclose can't clobber the replacement.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): account for delayed synthesized closes

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 05:01:20 -07:00
NeilandOrca fdb58695e9 [P1] fix(checks): stop skipped and manual checks reporting as failures (#11700)
* fix(checks): stop skipped and manual checks reporting as failures

Route every check-classification surface through one shared helper so
desktop renderer, desktop main and mobile agree on the same verdict.

- GitLab `manual` jobs and pipelines are neutral again, not action_required/failure
- `skipped` counts as passed everywhere, including mobile
- a neutral check no longer demotes a summary that has passing checks

* fix(checks): move the check-classification parity test into the renderer project

The parity table lived in src/shared but imported a renderer module, and both
config/tsconfig.node.json and config/tsconfig.cli.json are composite projects
that include src/shared without that renderer path, so `pnpm typecheck` failed
with TS6307 on two of its three projects. Only the web project spans both trees.

Co-authored-by: Orca <help@stably.ai>

* fix(checks): stop the Tasks-grid pill contradicting its own verdict

The checks pill's label, tone and icon all read one ProviderCheckSummary, but
getChecksLabel short-circuited on the raw `neutral` counter while the tone and
icon key off `state`. After the classification fix a PR with 19 success + 1
neutral renders an emerald CheckCircle2 pill that reads "1 unresolved", and
mobile's own label (which keys off `state`) reads "19/20 passed" for the same
summary.

Move the label into src/shared/provider-check-summary.ts so desktop and mobile
cannot fork it again, and key it off `state`.

Also covers deriveWorkItemCheckSummary, the desktop-main producer of the summary
that reaches the Tasks grid and the relay-paired mobile client. It was rewritten
here with no test at all; the parity table stands in derivePRCheckStatusFromRollup,
which is a different normalizer. The new main-process test drives getWorkItem with
a real statusCheckRollup fixture, pinning the StatusContext `state` fallback that
would otherwise be deletable with the whole suite still green.

Co-authored-by: Orca <help@stably.ai>

* fix(gitlab): route the pipeline job-array rollup through the shared check classifier

The array path in derivePipelineStatus kept its own copy of the rollup rules, so
manual-only read green and one unrecognized job status demoted a passing pipeline
to neutral — both disagreeing with every other check surface.

Also retry the packaged-CLI smoke temp cleanup on Windows: the copied Orca.exe can
still be locked by AV/indexers after every assertion passed, failing the package job.

Co-authored-by: Orca <help@stably.ai>

* fix(gitlab): stop the skipped pipeline string diverging from the Checks tab

- classifyPipelineString now counts a skipped pipeline as passing, matching
  the per-check classifier; canceled stays neutral and is pinned as an
  explicit, sign-off-pending divergence.
- Pin the production string path (head_pipeline.status) in the parity table
  and note that the job-array branch has no production caller yet.
- Count skipped checks in the Checks panel's passing header so it agrees
  with the checks pill.
- Correct the packaged-CLI smoke retry comment: the EBUSY is the smoke's own
  just-exited Electron process, not AV/indexers.

Co-authored-by: Orca <help@stably.ai>

* fix(checks): finish cross-surface check parity and back out the skipped MR-card flip

Review follow-ups on the check-classification PR.

- PullRequestPage and GitHubItemDialog kept private copies of getCheckCounts /
  getChecksSummaryLabel that still counted only `success` as passing, so a
  2-success/3-skipped PR read "2 passing · 3 skipped" there and "5 passing" in
  the sidebar. Both copies move to pr-check-counts.ts, which routes the passing
  bucket through classifyCheckOutcome; action_required keeps its own amber
  bucket. The summary icon now keys off passing count, so an all-neutral PR
  stops painting a green tick above "0 of N checks passing".
- The sidebar checks header and triage strip still called
  `{status: completed, conclusion: null}` pending, contradicting the grey
  "Unresolved checks" pill. Both now read summarizeProviderChecks and render an
  unresolved chip/strip instead of an amber spinner that can never resolve.
- classifyPipelineString('skipped') is reverted to neutral. That flip painted
  MR cards green for pipelines that never ran, on the only GitLab path with
  production callers, and contradicted the same function's deferral of
  `canceled`. Both tone changes stay deferred, pinned by one test.
- classifyPipelineString('manual') resolves to pending rather than neutral: a
  blocked pipeline is outstanding, and neutral let the worktree card fall
  through to its emerald `open` default while GitLab still refuses the merge.
- TaskPage's checks pill helpers move to task-page-checks-pill.ts so the
  "1 unresolved on a green pill" fix is actually pinned by a test.
- smoke-packaged-cli no longer lets an EBUSY cleanup replace the real failure.

* fix(checks): stop completed unknown checks from spinning

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-31 04:58:15 -07:00