* fix(github): resolve project accounts at the request boundary
* fix(github): retain account identity through request settlement
* fix: isolate GitHub leaf caches by held execution identity
* fix: use canonical options type for merged review membership
* fix(wsl): user clicks on cwd-less gh may start the stopped default distro
#35709 stopped background gh/glab from booting a stopped default distro,
which also refused explicit clicks for users whose gh lives only in WSL.
Starring Orca, the auth diagnosis, the project picker, opening a work
item and board edits now run as a user act on the distro the fallback
uses; the board table reload, which also runs unprompted, stays gated.
The GitHub and GitLab rate-limit cards keep their last snapshot on the
not-running refusal instead of flipping to unavailable.
* fix(wsl): only an explicit click may start the stopped distro for project gh calls
Wrapping the project-view and auth-diagnosis IPC handlers as user acts
also let mount effects (useProjectViewTable listing a restored board's
views, GhAuthErrorHelp's diagnosis) start a stopped default distro. The
handlers now wake only when the renderer passes wakeWslDistro, which the
picker browse/select, opening an item and every edit set; mount effects
leave it unset, and the auth diagnosis never wakes.
* feat(native-chat): hover cards for GitHub PR and issue links
Links to the chat's own repo (origin or upstream) open a card with the
state, title, author, update time, and +/- or comment count. Bare #123
references in agent replies now link too. Cards use a light one-call
lookup; older hosts ignore the flag and answer with the full lookup.
* test(github): expect the preview options argument in runtime owner/repo lookups
* fix(native-chat): route GitHub link cards by the chat's own host and retry failed repo lookups
The repo lookup took the first worktree with the chat's id, so a twin on
another host could answer, and the preview cache keyed by repo id alone.
A failed slug lookup also left cards off until the chat remounted.
* fix(wsl): Source Control's PR push-target and Create PR checks never start a stopped distro
Showing Source Control resolved the linked PR's push target
(worktrees:resolvePrBase) and its Create PR eligibility
(hostedReview:getCreationEligibility); both read the repo's git config over
\\wsl.localhost and ran git and gh in the distro, starting a stopped one
(E9). Both now refuse a stopped distro unless the user is creating or
launching from the PR, or pressed Create PR (optional wakeWslDistro), in
the IPC handlers and their runtime twins.
* fix(wsl): gate the GitLab MR push-target lookup the same way
Source Control's push-target ensure also resolves a linked GitLab MR
(worktrees:resolveMrBase / worktree.resolveMrBase), which read the repo and
ran glab and git in a stopped distro. It now refuses unless the user is
creating from the MR, which passes wakeWslDistro.
* test(runtime): let the mocked running gate cover guardWslUncPathAccess too
Runtime specs fake win32 with no wsl.exe and mock blockedWslDistroForPath
to running; the real guard calls its own module's check, so the PR-base,
MR-base and eligibility gates refused there. The guard mock now asks the
mocked check, keeping per-test overrides working.
GitHub serves a renamed repository under its old name, so branch discovery
finds the PR through a stale origin URL, then rejects it because the PR
reports the new head repository name. Accept the PR when the queried
repository is origin itself and GitHub reports it as a same-repository PR,
and share that same-repository check with the cached-number lookup path.
Fixes#35687
Co-authored-by: Neil <neil@stably.ai>
Cwd-less gh/glab calls whose host CLI is missing fell back to the default
WSL distro with no running check, so background work (rate limits, auth
status, Settings, task-source probes) booted a stopped distro. The
fallback now asks the background exec gate first: stopped or
unverifiable refuses with the distro-stopped error, running and user
acts fall back as before.
* Send ordinary PR reviewer updates directly to the API
* test(github): trim redundant cases
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Reuse work-item page totals for matching count queries
* Verify page totals avoid later count requests
* Read numbered PR pages with reusable cursors
* Combine short cursor prefixes with the requested page
* Use advanced search for grouped PR cursor queries
* Validate combined quota changes against the final PR base
* Verify forced and credential-scoped PR pages bypass saved cursors
* Batch uncached PR reads and reuse equivalent PR totals
* Match Boolean PR totals to the advanced list predicate
* Keep malformed PR count groups on the scoped legacy search
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Read sidebar PR and stack metadata in one GraphQL query
* Preserve direct PR error recovery without duplicate GraphQL reads
* Keep combined sidebar queries out of merge preflight
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
gh pr list already scopes --search with repo:OWNER/REPO type:pr. The
extra is:pr Orca added made private-repo PR lists come back empty when
gh is authenticated with a GitHub App user token (ghu_).
Fixes#34536
* fix(github): count gh pr and issue commands against the GraphQL rate limit
gh serves pr/issue/repo view-style subcommands over GraphQL, but the breaker
filed every non-api command under core and only matched REST's
"API rate limit exceeded" wording, so GraphQL exhaustion never tripped it.
Map those subcommands to the graphql bucket, match GraphQL's
"API rate limit already exceeded", and guard project-view issue close/reopen
against the GraphQL quota.
Fixes#28767
* test(github): drop redundant Date.now() args in graphql breaker test
Keeps the flaky-test-pattern ratchet at its baseline.
---------
Co-authored-by: Neil <neil@stably.ai>
* fix(wsl): background repo probes, PR refresh and worktree scans never start a stopped distro (E9)
Any Windows touch of a \\wsl.localhost path, or Git/gh run for it, boots a
stopped distro. These background paths ran without asking the stopped-distro
gate: the worktree scan refresh (Git-admin fingerprint reads plus git worktree
list), the remote-identity and username enrichment sweeps, the startup
upstream backfill, the quiet-period ref maintenance, and queued PR refreshes.
Each now asks blockedWslDistroForPath first and skips, keeping known data,
until the distro runs; a host path never asks the gate.
* fix(wsl): a user act's wake allowance ends with the act
The allowance lived in an AsyncLocalStorage store, so any timer, listener or
queue created during a user act (opening a workspace, a wake read, the first
git status) kept it forever and could start the distro later, after a
terminate. The scope now carries an active flag cleared when the act settles.
* fix(wsl): skill discovery and automatic refreshPRNow never start a stopped distro (E9)
- Skill discovery (mounted by passive UI) leaves out roots on a stopped or
unverifiable distro, and skips plugin discovery for such a cwd, instead of
reading \\wsl.localhost; they are not reported missing, so the next scan
picks them up.
- refreshPRNow skips automatic reasons (visible, active, post-push) on a
blocked distro like the drainer does; only a manual refresh may start it.
- Ref maintenance's opt-out probe takes the same sync wslDistroForPath
early-out as the other paths.
- Tests: stopped-with-no-cache worktree scans answer from stored rows, and
'unverifiable' is treated as blocked.
* fix(wsl): skill discovery for a WSL target never starts a stopped distro (E9)
Passive UI discovers skills for projects whose agents run in WSL. The target
resolution probed the distro home (wsl.exe) and the scan ran in the distro,
so either booted a stopped one. Discovery now refuses a stopped or
unverifiable WSL target before the home probe and answers no skills and no
sources, so nothing reads as missing and the next scan retries. Delete and
share keep resolving without the gate.
* fix(wsl): a settled user act marks its distro woken; host skill scans never await the gate
- runAsWslUserAct (the gate's wrapper) notes the distro woken inside the act
when it settles, success or throw, so a follow-up is never answered by a
'stopped' read taken before the act.
- Skill discovery takes sync early-outs: a host-only root set and a host cwd
never await the WSL gate.
* fix(wsl): Share discovers as a user act; a stopped distro's skills read as unknown, not absent
- skills:prepareShare (and the skills.share RPC) discover as a user act on the
target's distro, so an explicit Share on a stopped distro finds its skills.
- A blocked WSL target answers an 'unavailable' source per requested kind, and
roots (or a workspace plugin folder) on a stopped distro are reported
'unavailable' instead of dropped, so the installed-skill verdict says the
status may be incomplete rather than offering Install.
* test(wsl): use a fixed fetchedAt in the stopped-distro PR refresh test
* fix(wsl): Share scans stopped distros' skill folders, with or without a target
The desktop Share dialog sends no target, so the user-act scope had no distro
and the gated scan reported a stopped distro's repo folders unavailable; Share
then failed with skill-share-source-not-found. discoverSkills takes
gateStoppedWslDistros: false for both share paths (IPC and RPC), skipping the
stopped-root split and the plugin-cwd check; passive discovery keeps the gate.
Gated and ungated scans of one target no longer share a coalesced answer.
GitHub issue close/reopen swallowed every gh failure mentioning "already"
(e.g. "API rate limit already exceeded"), and GitLab MR close/reopen did the
same for glab. gh exits 0 when the issue is already in the target state, so
every gh failure is now reported. For glab, only its fixed no-op refusals
("this merge request has been closed" / "this merge request is already open")
count as success; "has already been merged" and all other errors surface.
Fixes#28766
Co-authored-by: Neil <neil@stably.ai>
* fix(i18n): localize GitHub project view errors and states
Main now tags Orca-authored project-view failures with an optional
reason code plus host, rate-limit numbers and diagnostic detail; the
English message is unchanged for mobile, older clients and logs. The
renderer maps type/reason to translated copy in the error state, picker,
item dialog and mutation toasts, falling back to the English message for
older hosts and GitHub's own text. Also wraps the layout labels,
unsupported-layout, not-found/too-large copy, picker partial-failure
summaries and the gh sign-in help.
Closes#34589
* test: expect input_too_large reason on oversized project refs
* fix: make project view error copy switches exhaustive
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Main now tags each Orca-authored PR/MR create failure with a reason code
(the English error is unchanged for mobile, older clients and logs), and
the renderer maps the reason to translated copy in the Source Control
notice, the Checks panel and the pre-create blocked notice. Sign-in steps
are single-sourced across providers.
Fixes#33321
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
On repos whose base branch requires a merge queue and that disable
auto-merge, gh pr merge --auto always calls enablePullRequestAutoMerge,
which GitHub rejects. Call the enqueuePullRequest mutation directly in
that case and report enqueue failures as merge-queue errors.
Fixes#19638
Co-authored-by: Neil <neil@stably.ai>
Issue create (including the empty-body fallback and follow-up PATCH),
issue body edits, and issue comments passed the body as
`--raw-field body=...` on argv. Windows caps the command line at
32,767 chars, so bodies with pasted inline images failed with
`spawn ENAMETOOLONG` before reaching GitHub.
These calls now use `gh api -X <METHOD> <endpoint> --input -` with a
JSON payload on stdin. ghExecFileWithScopeAsync also now forwards
`options.stdin` to the child process; it was previously dropped.
Fixes#15832
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
* fix(worktrees): never leave the create-worktree dialog stuck on a stalled preflight
Bound the same-repo PR branch fetch with the review-head fetch timeout, and
hand a slow composer submit off to a staged sidebar row so the dialog closes.
* fix(worktrees): harden create-worktree handoff from adversarial review
Dedupe handed-off submits against linked creations resolved during preflight,
bound the staged sidebar row with a deadline, offer Reopen on handed-off
failures, and bound the GitLab MR branch fetch.
* test(worktrees): expect the bounded PR/MR branch fetch timeout
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* chore(lint): remove disable directives that suppress nothing
Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.
* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials
* chore(lint): fix misc suppressed lint violations instead of disabling
- consistent-type-imports: the "vi.importActual requires inline import()"
rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
bridge test mocks, drop blanket file-level disables, drop a this-alias.
* refactor(main): replace avoidable type assertions with checked narrowing
17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.
* refactor(renderer,mobile): replace avoidable type assertions with checked types
13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.
* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support
- safe-react-node-view-renderer patched handleSelectionUpdate, which
@tiptap/react 3.31.3 no longer has, so it always returned early. Use the
stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
file-level no-module-mocking disables in test harnesses and fixtures.
Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
then drop those disables and the now-redundant per-file exemptions.
* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)
Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.
* fix(runtime): validate loaded device rows and host scope instead of casting
Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
field" was false; deviceId, token, name and timestamps were never
checked. Rows without an identity are now dropped and the file is set
aside through the existing corrupt-file path (registry reads as
doubtful, so no revocation verdicts); a non-string token is stripped
so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
ignore unknown scopes (wire schema unchanged for mixed versions). The
remaining cast now states what it actually covers.
* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture
-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.
* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests
Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.
* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts
- work-item-routing cast a one-field object to AppState['settings'] under
a claim that readers only consume activeRuntimeEnvironmentId. Return the
existing RuntimeFocusSettings pick instead and narrow the cache-key
chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
message-bridge cast.
- native chat readability drops a redundant repo-list cast.
* test(renderer): replace repeated cast suppressions with typed fixtures
-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
argument order or fixture is now a type error; drop binding casts for
members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
stores use createTestStore; runtime environment rows/statuses come from
a typed paired-runtime-environment fixture.
* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob
* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero
The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
'session-insecure-origin') and a launch_source the telemetry schema
rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.
* chore(typecheck): gate the now-clean e2e typecheck in local lint
The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.
* test(mobile): typecheck every mobile test; baseline 105 files -> 0
mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.
- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
zod's optional marker, so every reply type built on it claimed
salvaged fields were always present. Return a real ZodOptional;
runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
RpcClient for send-only callers, a narrow fetch signature, and only the
request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
could never hold; a recovery hook ran with an undefined scope key;
a create-PR test cast to a never-imported type; "no URL" assertions
passed on null; plus stale required props, args and enum values.
* test(e2e): parse workerStart result instead of casting it
* test(runtime): typed partial runtime stores with production-shaped fixture data
Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
`[object Object]` (it took an id where the store takes a target), so
reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.
* refactor(renderer): resolve suppressed React hook lint instead of disabling it
109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
through controller objects; list them (identity never changes, so
nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
Landing preflight, automation catalog, structured-session recovery,
file explorer).
- The conditional layout-owner hook is selected once at module load from
the build constant instead of a rules-of-hooks disable that also hid
the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.
* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet
The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.
* test: merge duplicate shared/constants imports
* revert(file-explorer): restore original tree load effects pending SSH e2e investigation
* test(runtime): drop RuntimeStore cast now that the fixture is typed
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(github): reject cached same-branch PRs with foreign heads
* fix(github): keep same-repo cached PRs behind renamed remote URLs
A stale origin URL after a repository rename or transfer made the
foreign-head check reject the checkout's own same-repo PR and clear its
cache. Treat a PR whose head matches its canonical base URL as local.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>