mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
2dd67e83b24ab4004a48505257d82a3bdee92ee1
264
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2dd67e83b2 | perf(worktree): overlap configured path filesystem probes (#17453) | ||
|
|
976e05c0c8 | perf(worktree): overlap finalization head probes (#17443) | ||
|
|
1268fb56f1 |
fix(worktree): complete a create Git can confirm but cannot list (#17388)
* fix(worktree): complete a create Git can confirm but cannot list `worktree.create` verified against `listWorktrees`, which softens every git failure to `[]`. Any listing failure therefore failed a create whose worktree and branch `git worktree add` had already written, orphaning both, and reported only 'Worktree created but not found in listing' — the real cause reached the main-process console and never the user. Verify against the error-propagating listing instead, and when that fails or omits the row, rebuild the row by asking Git about the worktree itself. The direct read returns nothing unless Git resolves the path into this repo's object store with the expected branch checked out, so an unrelated or half-made checkout still fails the create. Fixes #16520 * fix(worktree): authorize a recovered create and reject an unreadable HEAD Review follow-ups on the create-verification fallback: - register the recovered worktree's own root, additively, so the create the user just made is not rejected by filesystem/git-status IPC - treat an unreadable HEAD as no recovery instead of a blank OID - keep the direct read's failure when the listing merely omitted the row - skip the symlink cases on Windows and reset the new harness mock * fix(worktree): bound the create-recovery disk read and keep WSL paths case-sensitive Readiness-scan follow-ups: - deadline the filesystem common-dir read; a .git on a hung mount left the whole create IPC pending where it used to fail after the Git deadline - offer no disk candidate for a bare repo instead of a fabricated <repo>/.git - compare POSIX common dirs case-sensitively, so two WSL repos differing only in case are not accepted as one object store on a Windows desktop - move toGitOutputSpace to shared/wsl-paths as toWslExecutionSpace, next to the parseWslUncPath callers that already open-code it * fix(worktree): share one budget for create verification and keep recovered roots Three follow-ups from review of the create-recovery path: - The recovery no longer starts a fresh 30s deadline after the listing already burned one, so worst-case create verification stays at ~30s instead of ~60s. A 5s floor keeps the direct read a chance to answer when the listing spent the whole budget. - rebuildAuthorizedRootsCache now carries a repo's previously registered roots forward when its listing throws. A rebuild running while Git is still broken could otherwise un-authorize the worktree a create just recovered. - Corrected the scan-cache doc comment: it claimed strict and lenient listings coalesce, but the cache key includes the runner name precisely to keep them apart, so a strict joiner can never inherit a lenient scan's softened []. Each change has a negative control: reverting the hunk fails exactly its own test and nothing else. * fix(worktree): keep a recovered worktree authorized across roots-cache rebuilds The previous approach registered a recovered create into the same per-repo set the rebuild recomputes from `git worktree list`. That set is derived from the very listing that failed, so a rebuild would re-deny the worktree — either by overlapping the registration, or by simply listing again and omitting the row. Carrying old roots forward on a thrown listing did not cover either case. Recovered roots now live in their own additive layer that rebuilds union in rather than replace. The layer is retired on evidence, not on a timer: - the listing can see the worktree again (Git recovered), or - the listing succeeded and the directory is gone (worktree removed). A repo whose listing threw is left untouched, because a dead mount fails both the listing and the stat, and treating that as "removed" would revoke the worktree in exactly the outage this layer exists for. The layer is capped so it cannot grow unbounded, and survives cache invalidation deliberately: repo mutations are frequent and would otherwise re-deny a recovered worktree. Three tests cover the healthy-rebuild-omits-the-row case, the in-flight rebuild race, and retirement once the listing sees it again. Removing the union fails exactly the two keep-tests and nothing else. * perf(worktree): only read the repo's .git from disk when Git's own answer disagrees The disk read is a second opinion on Git's reading of the common dir, but it ran unconditionally as part of the same Promise.all. A deadline bounds the IPC, not the syscall: Promise.race cannot cancel an in-flight fs operation, and a `.git` on a hung mount (dead NFS/SSHFS, stalled WSL 9p) pins a libuv threadpool thread that no timeout can reclaim. AbortSignal would not help either — fsPromises.stat takes no signal, and a blocked syscall is not interruptible from userland. So stop paying it on the happy path: read from disk only when Git's own reading did not already confirm the common dir. Same accept/reject outcome, but the threadpool exposure now requires both a failed listing and Git disagreeing about the repo, instead of every recovered create. * fix(worktree): compare the disk common-dir witness in Git's execution space Exercising the fix on a real Windows host against WSL Ubuntu-24.04 found the filesystem second opinion is inert there. Node reads `.git` in the caller's space and answers `\\wsl.localhost\<Distro>\home\...\.git`, while Git-in-the- distro answers `/home/...`. isSameCommonDirPath refuses to compare a POSIX path against a Windows one, and canonicalizeLocalPath cannot bridge them because realpath on a Linux path from a Windows process is ENOENT. So the candidate could never match, and the one case that depends on this witness alone — a symlinked repo root on the Git 2.25 fallback — declined a worktree Git had already confirmed. Run the disk result through toWslExecutionSpace, the same translation readRepoLocation already uses. This is a false reject, not a false accept: it made recovery give up, never adopt the wrong repo. Verified on awin; the modern --path-format=absolute branch was unaffected because Git answers both sides itself there. * fix(worktree): retire a recovered root only on proof, never on a stalled probe The prune ran an unbounded stat and read every failure as removal. Two consequences, both in the outage the recovered layer exists for: a hung mount stalled the rebuild that gates filesystem auth, and a transient EACCES/EIO revoked a live worktree. The listingFailed guard did not cover either, because listWorktrees softens Git failures to [] and never throws. Prune now retires on definitive ENOENT only, probes in parallel under a deadline, and treats a stall as inconclusive. The capacity bound refuses a new root instead of evicting an authorized one, so an over-cap create is merely unauthorized rather than a live worktree being revoked. |
||
|
|
b5a85890ac |
perf(git): bound git subprocess execution with an atomic admission scheduler (#16874)
* perf(git): bound git subprocess execution with an atomic admission scheduler Field traces (#16038, #11363) show Windows freeze storms driven by unbounded concurrent git children (12+ at once, 50-65s status convoys for 25+ minutes). Admit every main-process git child against atomic per-budget base+headroom counters (general / network / per-route), with reserved interactive capacity, ordering-only aging, close-bound permit release, a 120s fail-safe read timeout that feeds scheduler backoff, tier plumbing through every option carrier, and coalesced+jittered visibility pollers. Killswitch: ORCA_GIT_ADMISSION_DISABLED=1. Storm harness A/B: max concurrent children 65 -> 6, interactive p95 791ms -> 88ms; output-parity battery byte-identical with admission on vs off. * test(git): run the admission output-parity battery on every platform Parity needs real git, not the storm harness's PATH stub, so it must not share that file's POSIX gate - Windows is the platform where parity evidence matters. * fix(git): preserve interactive admission invariants * perf(git): keep admission queue drains linear * fix(git): close final admission gaps * perf(git): bound eligible route selection * fix(merge): remove unrelated stale snapshot changes * fix(git): preserve refresh lifecycle authority * test(git): align admission lifetime contracts * fix(git): harden admission across runtime paths * fix(git): restore freshness for bulk status reads * test(git): repoint delete-dialog source pins after admission plumbing The hydration effect now orders its targets through orderDeleteWorktreeStatusHydrationTargets and passes includeLineStats alongside the abort signal, so both literal anchors stopped matching. The invariants are unchanged and still pinned: dropping the signal, the main-worktree/folder filter, or getState-instead-of-subscribe each still reddens this test. * Fix git admission tier propagation and lock ordering Decode optional Git status tiers permissively and default runtime RPC status reads to the status lane while preserving renderer caller intent. Acquire the FETCH_HEAD mutex before atomic admission so same-repository fetch waiters hold no global or route permits. Preserve automatic pull-request refresh reasons, keep explicit hosted-review refreshes interactive, remove the dead candidate tier, and keep relay scheduling unchanged. Use tier-aware status lease keys because a shared lease cannot be safely promoted after its admission request is queued or granted. * test: align expectations with admission plumbing * refactor(child-process): move the process contract types to process-spec run-process.ts crossed its line cap after gaining the termination observer; the public types and defaults move out with re-exports so no caller changes. * chore: restore pnpm-lock.yaml to main (unintended local drift) --------- Co-authored-by: Merge Sim <sim@local> |
||
|
|
3ab9766e38 |
perf(worktree): prepare checkouts while the composer is open
Squashed merge of PR #17290. |
||
|
|
3af2c665c0 |
fix(cli): name PowerShell when it strips quotes from JSON flags (#17351)
* 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. |
||
|
|
037cc2e531 |
Split Git worktree responsibilities (#17253)
* Split speech session lifecycle * Split terminal output scheduler pipeline * Split mobile browser pane modules * Prune resolved max-lines suppressions * Split pane tree equalization logic * Extract mobile troubleshoot screen styles * Split external automation manager * Split main window service attachments * Split hosted review creation checks * Split automation dispatch event handling * Split settings navigation metadata * Split daemon initialization lifecycle * Split GitLab item dialog * Split relay dispatcher layers * Split mobile host screen * Retarget mobile view settings source test * Split runtime file client layers * Split ports panel layers * Split runtime environments pane layers * Split local PTY provider responsibilities * Split CDP bridge responsibilities * Split relay Git handler responsibilities * Track moved relay Git fetch audit * Split Linear item drawer responsibilities * Split telemetry event schema responsibilities * Split resource usage status responsibilities * Split remote terminal multiplexer responsibilities * Split Git worktree responsibilities * Fix F3-speech for #17123 * Fix F1-cycle for #17131 * Fix F4-navtest for #17157 * Fix F2-allowlist for #17161 |
||
|
|
3457acb647 |
fix(memory): hydrate retained PTYs before diagnostics (#17308)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
e3757a1d30 |
fix(worktree): ignore orphan remote-tracking refs in branch conflicts (#16699)
Refs #16646 Unify native, WSL, and direct SSH conflict checks behind the execution host, remove the duplicated SSH classifier, and cover orphan/configured remote behavior across both paths. |
||
|
|
96565fe370 |
perf(source-control): stop re-running every git read on each file selection (#15036) (#16600)
* perf(source-control): stop blocking main on four sync git-dir probes per status poll detectConflictOperation ran four existsSync calls against the git dir on every status poll. On a `\\wsl.localhost\...` worktree each one is a 9p round trip, and being synchronous they landed on the Electron main thread back to back. Replace them with concurrent fs/promises access probes: same "any failure reads as absent" semantics existsSync had, one wave instead of four serialized blocking calls. The outer try/catch went with them -- neither resolveGitDir nor the probes can throw now, so it was unreachable. Part of #15036 (source-control latency). * perf(wsl): let git reads take the shell-free route from a cwd-derived distro shouldAttemptWslDirectGit required options.wslDistro, so a `\\wsl.localhost\...` worktree without a resolved WSL project runtime never qualified -- even though the distro is right there in the cwd and wslDistroForCommand already knew how to read it. Every `git show` behind a diff therefore ran through the user's login shell, executing their rc once per blob read. Three changes: - Derive the distro from the cwd when no override was supplied. This is the fix; the routing decision now depends on where the repo actually lives. - Wait, bounded, for a cold read-environment probe instead of resolving without it. The probe is one wsl.exe call shared per distro, so the wait is paid at most once, and past WSL_GIT_READ_ENVIRONMENT_WAIT_MS the shell route runs exactly as before. It returns null rather than a settled promise when there is nothing to wait for, so a non-WSL git call is not pushed into a later microtask. - Opt the blob reads into preferWslDirectGit via gitReadOptionsForWorktree (renamed from gitStatusReadOptionsForWorktree; it was never status-specific). Belt-and- braces only: `show`, `config --get-regexp`, `ls-files` and `rev-parse` were all already matched by isWslDirectGitReadCommand, so this changes no routing today -- it just stops the diff path depending on a heuristic it knows the answer to. git-blob-read also gains a `failed` flag distinguishing "git ran and reported the path absent" (exit 128) from "the read never got an answer"; nothing consumes it yet, the settled diff cache does. Part of #15036 (source-control latency). * perf(source-control): give diff reads a settled cache keyed on stamped git state gitDiffReadDedupe coalesces only while a read is in flight, so every file selection re-ran the whole read: a `git config --file .gitmodules` spawn, one or two `git show` spawns, and a working-tree stat+read. On a WSL/UNC worktree each git spawn is a wsl.exe invocation, which is the ">3s Loading diff..." in #15036. Correctness first -- a stale diff is worse than a slow one. The cache never expires on a clock and there is no TTL to tune. Instead: - worktree-diff-stamp.ts takes a subprocess-free stamp of exactly the inputs a file diff is built from: HEAD (by resolved tip *content*, so a commit is visible even though HEAD's own bytes never move), `.git/index` (mtime+size), `.gitmodules` (submodule routing), and the working-tree file. A linked worktree's commondir and the packed-refs/reftable fallback are handled; an unborn branch is caught by recording "no loose ref" rather than only the packed stamps. - The stamp is captured BEFORE the read and stored with the result. Anything that moves during or after the read leaves the stored stamp behind, so the next lookup misses. That, not a freshness window, is why a stale diff cannot be served. - A store is refused unless the stamp was taken a full mtime bucket (2s, FAT's granularity) after its newest component. Below that, a second write inside the same bucket would be invisible -- git's own racy-index rule. - `null` stamp means "cannot prove" and never caches: a folder workspace, a repo whose layout cannot be read, or a filesystem reporting no usable mtime. - Submodule routes and reads that failed rather than proved absence are not reusable. A wsl.exe hiccup produces the same empty left side a new file does, and pinning that would persist a wrong diff. - invalidateGitReadCaches clears it and bumps a generation, so a read that started pre-mutation cannot store its result post-mutation. `ino` is deliberately optional in the working-tree component: Windows reports 0 for it on the redirector behind `\\wsl.localhost`, and requiring an unstable 0 to match would make the cache silently never hit on the exact host it exists for. Cache counters are exposed for the same reason -- a miss storm and a cold start otherwise look identical. Also drops gitDiffReadDedupe.clear() from getStatus. A status poll is a read; all it did was destroy a live coalescing entry so a concurrent identical request started duplicate git work. Mutations still invalidate through the shared point. Memory is bounded by retained characters, not entry count -- one diff result can legitimately hold megabytes. Fixes the source-control half of #15036. * perf(source-control): reuse BoundedMap and stop the WSL probe wait from outliving its answer Review follow-ups on the settled-diff-cache work: - SettledDiffCache now sits on the shared BoundedMap instead of hand-rolling the same Map + character ledger + evict-oldest loop. - pendingWslDirectGitReadEnvironment returns null once the probe has settled either way, so a distro whose direct route was disabled no longer pays for a 1.5s timer and two microtask hops on every git read. - That wait now honours the read's abort signal and goes through withTimeout, so an aborted read is not held for the full bound and a probe rejection can never surface as a read failure. - The settled-cache generation fence is taken before the stamp read, so a mutation that lands entirely inside the stamp's stats can no longer store an entry whose stamp is torn across it. - The cache counters are folded into the main-thread churn probe report, which is what tells a permanently-cold cache apart from a cold start in the field. * fix(source-control): tell WSL clock skew apart from a genuinely fresh write The racy-write margin compares two clocks: capturedAtMs is this host's, while the component mtimes come from whatever wrote the files. On a \\wsl.localhost worktree the guest sets them, so a guest running ahead pushes every recently-touched file past the margin and the cache refuses to store — for as long as the skew lasts, on exactly the platform this cache exists for. Nothing was wrong with the refusal; it was invisible. racyWrites alone cannot distinguish "the repo was just edited" from "the clocks disagree and this will never resolve on its own", so a permanently cold cache looked like a cold start. isDiffStampClockSkewed flags the one thing no local write can produce — an mtime in this host's future — and the cache counts those separately as clockSkewedWrites. A nonzero count is the signal that the cache is off for a reason idling will not fix. Found by review of #16600; behavior is unchanged, only observability. |
||
|
|
ef0d5931bc |
fix(source-control): budget WSL bulk git command lines by bytes, not path count (#16634)
Selecting ~100 changed files in a WSL worktree and hitting Stage All did nothing: the files stayed unstaged and the operation reported a failure. Bulk stage/unstage/discard chunked pathspecs 100 at a time, a count picked against a raw argv. A WSL-routed write is not a raw argv -- it is folded into one login-shell command line that shell-quotes every pathspec, quotes the result again, and embeds it three times (one branch per guest shell), so the finished line runs ~3.4x the raw pathspec bytes. Realistic project paths blew past the 32767-character CreateProcess cap at 100 paths and wsl.exe refused to spawn, with nothing staged. Chunking now measures the finished command line through the real resolver, so the wrapper's quoting rules live in one place and native, WSL and SSH hosts each get the budget of the host that actually spawns. A pathspec too long to fit alone still ships alone rather than being dropped, and no chunk is ever emitted empty -- a pathspec-free `clean -ffdx` would have swept the whole worktree. The tracked-path listing behind that discard also fences the WSL login shell now. Its stdout was parsed NUL-delimited without a fence, so Ubuntu's interactive rc banner glued itself onto the first record: that path failed to match anything git reported and was treated as untracked, sending a tracked file to `git clean` instead of `git restore`. Not observing a path in ls-files output is not evidence the path is untracked. The Windows command-line cap and its libuv-aware length estimate move out of the WSL runner into src/shared/windows-command-line-budget.ts, shared by both callers. |
||
|
|
44a3ba59e6 |
test(git): isolate worktree-shared-directories git config portably (#16582)
`os.devNull` is `\\.\nul` on win32. Git normalizes it to `//./nul` and rejects it as a config path, so every `git` call in this suite threw in `beforeEach` and 15 of 19 tests failed on Windows. POSIX resolves the same constant to /dev/null, which Git accepts, so CI never saw it. Point GIT_CONFIG_GLOBAL at a real empty file in a private mkdtemp directory, matching how skill-git-tree-identity and skill-windows-workspace already isolate, and use GIT_CONFIG_NOSYSTEM instead of GIT_CONFIG_SYSTEM. Set both on `process.env` rather than only on the suite's `git()` helper. `resolveWorktreeSharedDirectories` runs its own `git check-ignore` through the production runner, and `GitRuntimeOptions` carries no env, so the runner inherits `process.env`. The per-call override never reached the code under test: a host `core.excludesFile` could make a fixture that is not gitignored come back as ignored. Fixes #15409 |
||
|
|
933345d347 |
Clarify upstream divergence stats for rebased branches (#16358)
* Clarify upstream divergence stats for rebased branches When a branch is rebased, it still tracks the pre-rebase upstream while comparing against the new base. Move upstream arrows to the head line to prevent them being confused with compare-base counts. * Show upstream divergence stats independent of compare base Measure HEAD against upstream regardless of compare-base state, so divergence indicators stay visible even when comparison is missing, loading, or failed. Also use cross-platform temp paths in tests. * Show commit counts against compare base, not upstream Upstream divergence (↑/↓ against tracking branch) was confusing for rebased branches — the counts appeared beside the base ref but measured against the upstream branch. Show only the compare base count instead, on the line that names it. * Report branch divergence in both directions Rebased branches are typically ahead AND behind their base; a single count hides this case. Use symmetric range with --left-right --count to capture both directions efficiently, then expose commitsBehind in the UI alongside commitsAhead. * Use semantic names for i18n keys and template variables Rename hash-based translation keys to descriptive identifiers and replace generic value0/value1 placeholders with semantic variable names like `count` and `ref`. Improves code maintainability and makes translation strings self-documenting. |
||
|
|
5479bd9159 |
refactor(task-page): split task page into focused modules (#15163)
* rm unused files * rm unused files * fix(task-page): clean readiness lint findings * Add GitLab IPC timeout wrapper and improve error handling - Extract GitLab timeout logic into reusable `withGitLabIpcTimeout` wrapper to protect all GitLab API calls from hanging indefinitely - Apply timeout protection to all GitLab list and fetch operations - Add error handling for GitHub and Linear issue creation operations - Fix event bubbling in GitHub work item row to prevent nested button clicks from opening detail page - Remove unused `usePRReviewCellState` hook - Consolidate redundant imports * refactor(task-page): extract components and improve provider handling - Add glab timeout handling (30s) to prevent IPC thread blocking - Extract GitHub assignee/review components to dedicated files - Improve GitLab work item row keying (repoId:id) and keyboard event handling - Add context-aware error handling for Jira creation failures - Refactor GitHubAssigneeAvatar to use shared GitHubUserAvatar component * Add timeout support and error handling for GitLab operations - Admission control times out queued work after 30s to prevent indefinite queueing behind saturated operations - Mutation errors now display to users via toast instead of failing silently * Consolidate workspace attachment labeling into unified utility Extract common label-generation logic from GitHub and Linear work-item components into a single getWorktreeAttachmentLabel function, removing duplication across attachment types. * Improve TaskPage accessibility, i18n coverage, and error handling - Add missing aria-labels, roles, and semantic attributes for improved screen reader support - Extract hardcoded UI strings into i18n system with translate() calls - Add error handling and proper abort signal support for async operations - Use locale-aware date formatting throughout - Fix pagination disabled state and reviewer suggestion merging logic - Improve async state management with proper refs and effects - Add Textarea component import for Jira dialog * Improve TaskPage accessibility and i18n key naming - Add DialogTitle/Description with i18n to Linear issue dialog - Use useId to improve aria-labelledby in GitHub selectors - Replace hash-based i18n keys with semantic names - Use Object.hasOwn instead of `in` for safer filter checks - Fix PR review cell to clear input only on success * Add missing dependencies to TaskPage hooks and useCallback/useEffect arr Fixes exhaustive-deps warnings by adding missing setters, refs, and computed values to dependency arrays. Refactors GitHub and Linear issue state handling to compute values from pageData where available, with fallback to local state. Moves imperative ref updates into useEffect to properly track dependencies. * Fix TaskPage ref timing and null repo selection state Treat null newIssueRepoId as a valid selection, and use useLayoutEffect to synchronize the provider context ref before paint rather than after. * Extract Linear issue dialog components and fix popover scroll styling - Consolidate scroll styling: apply popover-scroll-content and scrollbar-sleek classes to PopoverContent wrappers - Remove redundant max-h-60 overflow-y-auto styles from inner picker divs - Fix GitHub new issue repo selection to explicitly target first selected repo on fresh mount - Correct CacheEntry import paths from store/slices/github to store/github/cache-model - Update tests to reference extracted dialog components instead of TaskPage.tsx * Improve GitHub task page i18n and fix issue creation edge cases - Add i18n support to GitHub work item aria-labels (draft PR, PR, issue) - Optimize work item row by extracting repeated source context call - Add safety check to prevent opening detail page when issue URL is missing - Fix dependency reference in detail opener hook - Extend GitLab job trace timeouts (60s backend, 65s frontend) for slow logs * Increase GitLab job trace fetch timeouts Job traces can outlive the runner's 30-second default timeout. Extend fetch operations to allow 60–65 seconds to complete. * Verify sourceContext variable extraction in github row test Update expectations to check that sourceContext is assigned to a variable rather than called inline, matching the refactored component implementation. |
||
|
|
822087c8ec |
refactor(git): split runner.ts into focused command-runner modules (#16395)
* refactor(git): split runner.ts into focused command-runner modules * chore(ratchets): repoint child_process and wsl.exe allowlists at the split modules --------- Co-authored-by: Neil <n@example.com> |
||
|
|
33587fb77d | refactor(git): split status.ts into source-control modules (#16393) | ||
|
|
07b13c8468 | refactor git repository host boundaries (#16177) | ||
|
|
7e76bb3aec |
Fix rebase race by fetching to private ref before rebasing (#15990)
* Fix rebase race by fetching to private ref before rebasing
`git pull --rebase` is vulnerable to concurrent fetches modifying remote-tracking refs during execution. Fetch to a temporary private ref (refs/orca/rebase/*) first, then rebase from that stable ref to avoid the race condition.
* Fix rebase race by fetching to private ref with timeout
Concurrent fetches can interfere with remote-tracking refs between
fetch and rebase. Use a unique private ref and 60-second timeout to
isolate each rebase operation and prevent hangs on stalled remotes.
Extract gitPullRebaseFromBase to a dedicated module.
* fix rebase race by fetching to private ref with timeouts
Concurrent fetches can replace FETCH_HEAD and remote-tracking refs between
fetch and rebase, causing the rebase to fail. Fetch to a temporary private
ref instead, use --no-write-fetch-head when available (Git 2.29+), and
serialize FETCH_HEAD access for older versions. Add process termination
barriers to ensure proper cleanup and extend timeouts for SSH operations.
* Fix rebase race by fetching to both private and tracking refs
Concurrent fetches between source and rebase can replace remote-tracking refs,
causing rebases to use stale bases. Now fetch to both a private ref and the
remote-tracking ref simultaneously, ensuring the tracking ref stays current.
Also improves process termination for WSL guests with process-group tracking,
fixes process-tree termination timeouts on POSIX, and serializes FETCH_HEAD
operations for linked worktrees through their shared Git directory.
* Add WSL setsid --wait probe and barrier termination timeout
Probe for `setsid --wait` support and fall back to unwrapped execution for BusyBox compatibility. Add a deadline for process termination barriers to prevent hanging when tree termination cannot be verified. Update tests for cross-platform compatibility.
* Add wsl-process-group-termination to WSL invocation allowlist
* Serialize per-worktree git mutations to fix rebase race
Introduce operation locking for each worktree to prevent concurrent
mutations (like rebase) from interfering with each other. Ensures
rebasing a linked worktree doesn't affect the source worktree state.
Add SIGKILL fallback if process termination barriers cannot verify
tree termination.
* Serialize pull and fastForward operations per-worktree
- Extract generic git operation lock to reuse locking pattern
- Refactor existing locks to use the generic implementation
- Apply per-worktree serialization to pull and fastForward to prevent races
* Route WSL group termination through runWslProcess
|
||
|
|
202d74a8a4 |
fix(git): enable Windows long paths for worktree creation (local, sparse, and SSH hosts) (#15866)
Co-authored-by: hwantage <hwantagexsw2@gmail.com> |
||
|
|
af2e825626 |
fix(worktree): stop warning about a stale local base branch that does not exist yet (#15331) (#15871)
Co-authored-by: vam <a@a.com> |
||
|
|
e9e238c883 |
refactor(wsl): delete the environment-policy layer the reviews kept failing on (#16007)
* refactor(wsl): delete the environment-policy layer the reviews kept failing on
A design council (Opus, Grok, GPT-5.6-Sol) reviewed the merged runner after it
took eleven review rounds to land. All three reached the same conclusion: the
invocation half is sound, the environment/probe half is not, and every round had
been debugging the second one.
The finding that settled it, from Opus: `environmentResolved` had **54
references, all in tests and the runner itself. Not one production reader.** The
safety mechanism the strict default existed for was never wired to anything, so
all 19 degrading sites reported absence with full confidence anyway -- #9725
live at every one, under comments claiming it was handled. Two of those comments
say so out loud; I wrote them.
Root cause, in one line: every knob existed only because a failed probe was
fatal. So it no longer is.
- `allowDegradedEnvironment` and `WslGuestEnvironmentUnavailableError` are gone.
A missing login PATH is a fact in the result, not an exception. That deletes
23 opt-outs, six catch-and-remap blocks, the transient/rejected cooldown
split, `probedWithBudget`, and the 1.5x re-probe heuristic -- none of which
had a reason to exist once the case stopped throwing.
- `lane` + `allowDegradedEnvironment` collapse into `loginPath: 'none' |
'preferred'`. 19 of 23 sites passed the opt-out, and two said in comments that
they did not want the login PATH at all: the flag had become the `'none'` the
union was missing.
- The `interactive` lane is deleted. It had zero production callers and kept ~30
lines of fence plumbing alive for tests only.
Net -98 production lines; the runner itself sheds 86 for 38.
Also carries three fixes from the W3 orphan-PR sweep I had not done:
- `WSL_UTF8=1` in the runner. My relay migration deleted the only place setting
it, so wsl.exe's own error text arrived UTF-16LE and read as NUL-riddled.
A regression I introduced. Credit: #9010 (Chang-Jin-Lee).
- `GITLAB_HOST` is now named in WSLENV, so a ported self-hosted host actually
crosses into a distro-routed glab (#12557). Credit: #12558 (makoto-developer).
- The WSL skill-setup command pipes into `sh` instead of `eval "$(...)"`, whose
nested quoting produced `word unexpected (expecting "in")` (#14292). Credit:
#14785 (innocarpe).
* fix(wsl): restore the login PATH for the Codex availability lookup
loginPath:'none' on a PATH lookup reports an nvm-installed codex as absent,
which is #9725. A miss without a resolved environment is now 'could not
check', not 'not installed'.
Also hardens the guards that should have caught it:
- bashism ratchet is per-call, not per-file, and fails closed on lexer desync
- blankStringContents handles regex literals (an apostrophe in /'/g desynced
the lexer, so the scan silently found zero calls)
- windowsHide allowlist 85 -> 80, stale once the lexer parsed those files
Credit: Grok (P0), GPT-Sol (ratchet gaps).
* test(wsl): close the two ratchet gaps that let planted spawns pass
- variable-indirected wsl.exe (`const b = 'wsl.exe'; spawnProcess(b)`) is now
tracked, so the 5 files recorded only in a comment become real allowlist
entries. Three actually spawn that way; the other two never spawned wsl.exe
at all, so the prose record was wrong by three in the hiding direction.
- promisify(renamedAlias) is now resolved, so `const run = promisify(execFile)`
behind an `execFile as x` import can no longer skip windowsHide.
Each verified by planting the violation, watching it fail, restoring, watching
it pass. Credit: GPT-Sol.
* fix(source-scan): stop the regex-literal reader from eating block comments
At index 0 there is no preceding token, so a file opening with a banner
comment had its `/*` read as a pattern and swallowed to the next slash --
110k characters of preload/index.ts, in the direction that hides offenders.
Measured across the tree, old lexer vs new: worst-case over-blanking drops
from -110564 to -1116 characters, and files that desync drop from 51 to 22.
The remaining extra blanking is regex interiors, which is the intent.
Regression tests for both lexer bugs, each verified to fail with its fix
reverted. The first draft of the comment test did not bind -- it asserted on
text after the swallowed span.
* fix(wsl): restore the unverifiable signal on the two remaining probe sites
Round 2. Three call sites used to throw when the login-PATH probe failed;
the redesign rewired one (Codex) and left two reporting confident absence.
- skill-wsl-provider-detection: the script ends in `|| true`, so a lookup
without the login PATH exits 0 with empty stdout -- identical to 'nothing
installed'. Callers skip the ~/.codex and ~/.claude skill roots on an empty
list, losing an nvm-installed provider's skills.
- wsl-cli-installer: the dead catch is replaced by an explicit check. Its
`case ":$PATH:"` probe otherwise answers from the distro default PATH and
Settings states as fact that the CLI is not on PATH. Timeout is checked
first, since a timed-out run also leaves the environment unresolved.
Also narrows the regex-literal prev-token set. '!', '+', '-', '>' and '}' are
value terminators as often as operators, so postfix `n-- / 2` and JSX
`<A size={14} /> : <B` were read as patterns and their spans blanked -- 13
live JSX spans, and one swallowed execFile call that left no desync behind.
False negatives only risk a desync, and desync fails closed.
Plus: WSL_UTF8 on the probe spawn (#9010 reached the runner, not the probe),
and the allowlist header I shuffled by sorting comments along with entries.
Credit: Grok (both P1s), Opus (lexer false positives).
* docs(wsl): drop the lane comments the redesign made false
The interactive lane is gone, so 'both lanes' and the fenced-stdout note
described code that no longer exists. Also states plainly that
environmentResolved is always true under loginPath:'none' -- the field cannot
rescue a PATH lookup that was mislabelled, which is how #9725 came back.
Credit: Grok.
* fix(wsl): stop piping user scripts into the shell's stdin
The W3 migration moved hooks from `wsl.exe --exec bash -c <script>` to a
script piped into `bash -s`. Anything the script runs that reads stdin then
drains the rest of the script, bash hits EOF and exits 0, and the caller logs
success -- an orca.yaml hook of `ssh -T git@github.com || true` followed by
`pnpm install` silently never installs.
Scripts now travel in argv by default, which is what the pre-migration code
did and what --exec makes safe. `scriptDelivery: 'stdin'` stays for the one
caller that needs it: the hook-relay installer embeds a base64 JS bundle far
past any command-line limit, and reads no stdin.
A runner test already described this exact EOF hazard -- for the login shell,
not for the guest command it was itself creating.
Credit: code review.
* fix(skills): make the unverifiable check unconditional, and stop double-probing
Round 3.
- provider detection threw only on an EMPTY result, so a degraded partial hit
slipped through: `claude` visible on the default PATH via Windows interop
plus an nvm-only `codex` returns a plausible ['claude'], and the caller then
skips the ~/.codex skill roots for a provider that is installed. The
installer already got this right with an unconditional throw.
- three sites asked for 'preferred' without needing it. The GROK_HOME probe
runs its own `"$login_shell" -lc`, so the runner's probe was a second login
shell eating up to half an 8s budget; the two skill scans are
find/base64/head/printf/stat over $HOME.
- the indirection binder missed `private readonly x = 'wsl.exe'` (the
modifier was captured as the name), backtick literals, and
`spawnProcess(this.x)`. Commit
|
||
|
|
98c03fe12f |
fix(win32): hide the console window for agent-browser and git helpers (#15887)
* fix(win32): hide the console window for agent-browser and git helpers W1 routed most child processes through `runProcess`, which always sets `windowsHide`. Six call sites still spawn directly, so each one opens a real console window on Windows: it flashes and steals foreground. For the git status poll, that is once per poll (#10488). A ratchet now scans every file that imports `child_process` and fails on a call without the flag. Its allowlist starts at the 76 files that still offend and can only shrink — it doubles as the worklist for routing them through the chokepoint, which is where the flag stops being a per-call-site decision at all. Diagnosed in #14589; the SSH and cookie-import sites it also covered are already fixed on main by the W1 migration. Co-authored-by: OrcaWin <orcawin@users.noreply.github.com> * test(wsl): stop the exec-mode guard scanning historical release checkouts The cross-version e2e lane checks whole past releases out under `tests/e2e/.cross-version-checkouts/`. The guard walked into them, so on any machine that had run that lane it reported 21 offenders -- every one a copy of shipped code we cannot edit -- and failed. Skip dot-directories; the >500-file vacuity assertion still holds. --------- Co-authored-by: OrcaWin <orcawin@users.noreply.github.com> |
||
|
|
6efd4061dc |
fix(git): run WSL git reads without a shell (#15257)
* fix(git): run WSL git reads without a shell WSL-routed git ran through the distro user's interactive login shell for one reason: to inherit their PATH. That shell also runs the distro's rc/motd and writes it to the stdout callers parse, which is why #10917 reports a shell banner breaking GitHub source detection. A shell-free route already existed (`--exec /usr/bin/env PATH=... git`, added for status reads in #13207) but it was opt-in, and only gitStatusReadOptionsForWorktree opted in. Every other read -- remote get-url, config --get, log, show, rev-parse -- took the login shell on every call, so the reported parse never benefited. Classify reads at the resolver instead of at each caller. A read needs nothing the login shell provides, so it takes the direct route without the caller asking. Writes and network operations stay on the login shell: they can depend on credential helpers and ssh-agent that the user's profile sets up. Subcommands that both read and write (config, remote, branch, submodule) require an explicit read flag before they qualify, so `config --get` goes direct while `config user.email x` does not. `git show` blob reads stop forcing the login shell and go direct too. The fence stays on that path: the login shell is still the fallback when the environment probe is cold or rejected, and a banner there would become file content. Behavior note: the first WSL read per distro now also warms the environment probe in the background, so a cold read spawns one extra wsl.exe. Subsequent reads start no shell at all. * fix(git): keep queried WSL remote reads on login shell * style: format WSL runner test * fix(git): match WSL read markers positionally The conditional read markers were matched anywhere after the subcommand, so a positional argument that happened to share a marker's name routed a write shell-free: `worktree remove list` read as a listing, and `submodule foreach status` as a status query -- the latter can run arbitrary commands, including network ones. Split the two kinds of marker apart. `config`/`branch` are flag-marked and still match anywhere; `remote`/`worktree`/`submodule` are action-marked and must match the first non-flag argument. The queried `remote show` rule and the `symbolic-ref` arity rule are unchanged. Neither case is reachable today -- Orca issues no `submodule foreach`, and the worktree paths are its own CLI, not git argv -- but the loose match was the shape of the defect, not those two instances. * test(git): pin read routing behind global options Writes hidden behind -c/-C/--git-dir must not reach the shell-free route, and an unparsed global form must fall back to the login shell rather than guess at the subcommand. Both directions fail safe. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
b7f2e17712 |
fix(git): fence buffered WSL login-shell reads (#15060)
* fix(git): fence buffered WSL login-shell reads Two git paths force the login shell unconditionally and buffer its whole stdout, so the distro's rc banner lands in front of the payload: - gitExecFileAsyncBuffer backs `git show :<path>` blob reads and hands the bytes straight to the diff/blob viewer, so the banner is prepended to displayed file content. - buildNetworkSshPolicyEnv probes `core.sshCommand` and treats any non-empty answer as a user-configured wrapper. A banner reads as configured, so the code skips the `ssh -o BatchMode=yes` fallback and silently disarms the guard that keeps non-interactive SSH from hanging on a prompt. Fencing is opt-in per call site rather than applied to the login-shell branch as a whole: streaming consumers (`git grep`, `ls-files -z`) parse records as they arrive, so an opening marker would be glued onto their first record. Only these two, both buffered by construction, opt in. Blob content can be binary, so the payload is sliced out of the raw bytes; decoding to find the fence would corrupt it. The markers are exposed on the captured command because the shared module is bundled for the renderer and cannot reference Buffer. Note this path is not a rare fallback: `preferWslDirectGit` is only set by gitStatusReadOptionsForWorktree, so every other WSL-routed git call takes the login shell on every invocation. * test(git): use findLast for the ssh-policy call lookup Satisfies the code-quality rule that flags filter-then-index. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
3a9f40ed70 |
fix(wsl): read machine output from a fenced login shell (#15290)
* fix(wsl): read machine output from a fenced login shell Orca runs WSL reads through the distro's *interactive* login shell so PATH matches the user's own terminal (nvm, mise and asdf only install into rc files interactive shells read). An interactive shell also runs the distro's rc/motd, and stock Ubuntu 24.04 writes its "run a command as administrator" hint to stdout -- no user customization required. Every caller parsing that stream was reading the banner as data: statPath -> "To run a command as administrator...\n\ndirectory" readPath -> banner prepended to the contents of every file read preflight -> banner prepended to `gh --version` / auth output `.trim()` cannot recover any of these, so a WSL worktree's file explorer sees no valid entry types and file reads return junk. Three call sites had independently grown their own marker to survive this (`__ORCA_AGENT_PATH__`, `ORCA_WSL_GIT_READ_ENV_V1`, and a `>/dev/null` fd dance), which is the tell that it belongs in one place. Fence the payload once, in the shared builder, and hand callers a reader that returns just their bytes. The fence carries a per-call nonce so `cat`-ing a file that happens to quote a marker is not truncated. Exit status is preserved, so the ENOENT mapping still works. wsl-git-read-environment drops its bespoke marker and parsing. * test(wsl): fence the login-shell path-lookup boundary test It asserted a raw interactive login-shell read matched an absolute path, so the distro rc banner made it fail on any stock Ubuntu. It is part of the shell-contracts CI gate, where it skips on Linux and hid the break. * docs(wsl): record the guest command-execution contract Both failure modes are silent - the command runs, exits 0, and returns the wrong bytes - so the rules need to live somewhere a reader will find them before writing the next wsl.exe call site. * fix(codex): fence the WSL Codex identity probe buildWslCodexBinaryStamp reads the login shell's stdout positionally -- path before the first newline, version after -- through an interactive login shell. On a stock Ubuntu the rc banner lands ahead of the payload, so the first newline falls inside the banner and the stamp becomes path="To run a command as administrator..." with the rest as version. Both halves are non-empty, so nothing throws: the stamp is silently wrong, and an unstable stamp reads as "the Codex binary changed" and reissues the trust grant. The identity script ends in `exec`, so it never writes a closing fence; the reader returns everything after the opening one, which is exactly this case. buildWslCodexIdentityArgs becomes buildWslCodexIdentityProbe and returns the reader with the argv so the two cannot drift apart. The other three WSL Codex commands are deliberately left unfenced: availability is exit-code only, and app-server/login hand stdout to a long-running program. * fix(wsl): harden the capture fence after review - readStdout now takes the LAST opening fence, matching the lastIndexOf the wsl-git-read-environment marker used deliberately: a login shell can echo the command text before running it, repeating the fence. - local-worktree-filesystem throws instead of falling back to raw stdout when the fence is missing. The fallback silently reinstated the bug being fixed -- statPath would return the banner as a file type and readPath would return banner+contents, with no signal. Preflight keeps its fallback; its matchers scan the whole blob and tolerate a prefix. - The exit-status test asserted only that the script CONTAINS `exit $?`, which is true for any input and never executed those lines. It now runs a real distro and asserts status 2 reaches the caller, which is what statPath's ENOENT mapping depends on. - Corrected the doc: a sed backreference has no `$`, so `--` never rewrote it. Replaced with the positional and shell-local cases that were measured to differ. * fix(wsl): stop running a login shell for filesystem reads statPath/readPath/rm run coreutils at standard paths and shell builtins. They need nothing from the user's PATH, so there was never a reason to start a login shell -- and starting one is what put the distro's rc/motd on the stdout these callers parse. Fencing that output treated the symptom. Using a plain `sh -c` removes the cause: no profile, no rc, no banner, by construction. The fence and its missing-fence error go away with it. The fence stays where it is actually needed: the three places that must run the user's shell to resolve their PATH (the preflight CLI probe, the WSL git environment probe, and the Codex identity probe). Net -12 lines. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
6e8da1df8d |
fix(wsl): pass guest argv verbatim through --exec (#15039)
* fix(wsl): pass guest argv verbatim through --exec
`wsl.exe <...> -- <argv>` expands `$name` in every argument against the
guest environment before the guest ever runs. It does this even when no
shell is involved, so `-- /usr/bin/printf %s '$HOME'` prints /home/you.
Every WSL invocation went through that preprocessor, so scripts arrived
already rewritten: `awk '{print $2}'` lost its field reference, and a
POSIX script asking for the literal `$HOME` got the expanded path.
`escapeWslShCommandForWindows` tried to compensate by escaping `$`, but
it skipped any `$` preceded by a backslash, so a script containing `\$`
was still corrupted -- and half the call sites never applied it at all.
Route every invocation through `--exec`, which passes argv through
untouched, and delete the escaper. The direct-git path already used
`--exec`, so this is not a new compatibility dependency.
A guard test fails if the `--` form reappears anywhere in the tree.
Net -50 lines of production code.
* test(wsl): drop remaining escaped-dollar assertions
* fix(wsl): cover the --exec migration's blind spots
An audit of every wsl.exe invocation found sites the first pass missed,
including two it actively broke:
- config/scripts/wsl-git-shell-benchmark.mjs imported
escapeWslShCommandForWindows, which no longer exists, so the script
threw on startup. Its wslShellArgs helper also still used `--`; the
file already had an --exec helper, so route both call sites there.
- classifySubprocessCommand unwrapped `wsl.exe <...> -- <binary>` by
breaking on `--` alone. With every Orca spawn now on --exec it never
found the guest binary and bucketed all WSL subprocesses as plain
"wsl", losing the git/gh/glab breakdown. Break on either separator,
since foreign wsl.exe processes still use `--`.
CliSkillRuntimeSetup builds its setup command as a template literal
rather than an argv array, so no array-shaped search could see it. Its
decoder accepts both separators so commands persisted before this
change still decode.
The guard now scans config/ and tests/ as well as src/, and checks the
command-string spelling alongside the argv one — the two shapes that
have each shipped a regression. It skips comment lines so prose about
the old form stays allowed, and asserts it scanned a plausible file
count so a bad root cannot make it vacuous.
* fix(wsl): restore the guard's multi-line sensitivity
The guard matched line by line, so `'--',\s*'bash'` could not span a
newline -- and every argv array in this repo is formatted one element
per line, which is exactly the shape it exists to catch. Measured
against the pre-migration tree it caught 17 files before and 9 fewer
after. It now strips comment lines and matches the rejoined text, with
a case that pins the multi-line shape so this cannot silently return.
The program list is wider than shells now, which surfaced a false
positive: tmux takes a `--` separator followed by a program too
(`split-window ... -- cat`). Matching is scoped to files that mention
WSL rather than narrowing the list back.
Also:
- Replaced the `sed` regression case, which was vacuous. A backreference
contains no `$`, so it returned `bac` under both separators and would
have passed without the fix. The block claimed every case proved the
bug. Swapped in a positional argument and a shell local, both measured
to differ -- the positional is the shape `wslUncDirectoryExists` uses,
where `--` blanked `$1` so every existing directory probed as missing.
- windows-shell-args.test.ts derived its expected argv from
buildWslExecArgs, the helper under test, so six assertions would still
pass if it regressed to `--`. Spelled the expectation out.
- Dropped two comments citing the removed `--` behavior as rationale.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
|
||
|
|
8e9b5c908c |
fix(github): fail closed instead of running client git against a remote repoPath when the SSH provider is unregistered (#14945)
* fix(github): fail closed when the SSH git provider is gone getCurrentHeadOid and probeTrackedUpstreamBranches only routed through the SSH provider when one was registered. With connectionId set but the provider unregistered (dropped connection, not yet reattached) they fell through to client-side git with cwd pointing at the remote repoPath — on a machine with a same-named local path that silently answers for the wrong repository. getCurrentHeadOid feeds shouldHideMergedImplicitPR, so a wrong OID changes which PR the UI attributes to a worktree. Both now take their existing unknown path (null / probeFailed) instead, matching repo-default-branch.ts. Local and WSL routing is unchanged. * fix(github): preserve PR state when SSH probes fail * fix(github): keep failed SSH discovery unverifiable * fix(github): propagate SSH identity failures * fix(github): scope verified SSH identity probes * test(github): preserve tolerant resolver calls * fix(github): preserve indeterminate auth discovery * fix(github): isolate SSH repository probe generations * test(github): expose SSH probe generation in mocks |
||
|
|
eb3f6838af |
perf: coalesce git upstream status reads (#11697)
* perf: coalesce git upstream status reads * fix(git): repair upstream lease key imports and guard its field list The read owner imported the shared/types barrel deleted by #14447, and its push-target key hand-enumerated fields, so a new GitPushTarget field would silently share a lease between two different targets. The destructure now fails to compile if a field is added. Lease tests moved into their own file after #14728 split ssh-git-provider.test.ts. * test(git): enforce native upstream coalescing in CI The 10-caller benchmark only runs under ORCA_GIT_UPSTREAM_COALESCING_BENCH_JSON, so nothing in CI failed when the native/WSL lease was bypassed. Route status.ts through invalidateGitUpstreamStatusReads so the export has a production caller. |
||
|
|
9367169888 |
refactor(tests): split every oversized test file off the max-lines suppression list (#14728)
* refactor(tests): split oversized test files off the max-lines suppression list Every `*.test.ts`/`*.spec.ts` that carried an `eslint/oxlint-disable max-lines` directive is now split into focused, behavior-scoped suites that fit the 800-line test budget, with shared setup extracted into co-located `*-test-harness.ts` / `*-test-fixtures.ts` modules (300-line budget). 83 files became ~930; the largest output is 797 effective lines. `orca-runtime.test.ts` is intentionally untouched. Test bodies were moved by scripted line-range slicing rather than retyped, so assertions are byte-identical. The only permitted body edits were mechanical rebinding where a shared value moved into a harness (e.g. `tmpHome` -> `homes.tmpHome`). Registries that enumerate test files were updated in lockstep: - config/max-lines-baseline.txt: pruned 341 -> 258 entries (all 83 removed). - config/reliability-gates.jsonc: 33 gates repointed at the split files, with assertionRefs split per file where a gate's coverage now spans several. - .github/workflows/pr.yml: the real-zsh lane now lists the 4 split files that actually exercise zsh, so they keep running in the dedicated shell lane. Also renamed agent-hooks `server-test-fixtures.ts` to `server.test-fixtures.ts` so the global-fetch call-site audit keeps skipping it, and added `.js` extensions to the CLI suites' dynamic harness imports (node16 resolution) to unbreak `build:cli`. Verification: full suite 52,449 passing vs 52,448 at baseline with zero assertions lost; `pnpm lint`, `pnpm typecheck`, and `pnpm build:cli` all exit 0; the terminal-pane e2e spec runs 31/31 headless. * refactor(tests): split hook-idle arbitration suite that oxfmt pushed over budget The pre-commit oxfmt pass reflowed pty-connection-hook-idle-arbitration.test.ts to 811 effective lines, 11 over the test budget. Split the hook-completion side effect and replacement-agent veto cases into their own suite; both files now sit well under the cap and the 15 tests are unchanged. * test: port upstream test changes into the split files after rebase Rebasing onto main surfaced 27 tests that main had added to files this branch deleted, plus edits to tests that had already moved. Taking the deletion side of those modify/delete conflicts would have dropped that coverage silently, so each upstream change is ported into the split file that now owns the behavior — for example main's six orchestration mailbox tests land across orchestration-runs, -send, and -check. Also repoints `orchestration.notification-mailbox-consistency`, a gate main added after this branch's gate remap, at those same three split files, and re-prunes the max-lines baseline against main's (257 entries). Verified: all 27 upstream test titles present; full suite 52,761 passing with the only diff vs baseline being 12 tests main itself removed and 3 that moved from skipped to passing; lint and typecheck exit 0. * fix(test): flush pending continuations before tearing down terminal test globals CI shard 5/16 failed on both Node 24 and 26 with `ReferenceError: window is not defined` from pty-connection.ts, surfacing through pty-connection-daemon-snapshot-replay.test.ts. The reattach/settle chains `await` a real promise and then touch `window.api`. Under fake timers those continuations cannot run, so they only become schedulable once restoreTerminalTestGlobals() switches back to real timers — which previously happened immediately before `delete globalThis.window`, so a late continuation threw and failed the whole file. Flush async ticks in that window instead. This is latent in the source rather than new: the pre-split 25k-line file kept running other tests after these, which gave the chains time to settle before teardown. Splitting the file moved teardown directly behind them. * fix(test): keep an inert window after terminal test teardown instead of deleting it The async-tick flush was not enough: the reattach/settle chain can resolve after teardown regardless of how long we drain, so CI shard 5/16 still failed with `ReferenceError: window is not defined` from pty-connection.ts. A real renderer never loses `window`, so deleting it was the artificial part. Swap in an inert proxy whose properties resolve to callables and whose calls resolve to undefined, making a late `window.api.pty.*` call a harmless no-op. The next test replaces it wholesale via installTerminalTestGlobals(), and no test asserts that `window` is absent. |
||
|
|
ab9d1a29a9 |
fix(worktree): never reissue a generated workspace name (#14350)
* fix(worktree): never reissue a generated workspace name
Generated workspace names were deduped only against currently-live
worktrees, so deleting a workspace returned its name to the pool. A later
workspace could draw the same name, land on the same directory path, and
inherit the previous occupant's agent conversation history — coding-agent
CLIs key their prompt history and transcripts by cwd.
Names are now retired permanently per repo. The registry is written in
main with the name Git actually used (the create loop can advance past a
requested name on collision), and seeded once per run from workspace
directories and surviving agent transcript buckets so already-spent names
are excluded from the start. Suggestions degrade to -2, -3 variants
instead of recycling, and those variants retire too.
User-typed names are untouched: retirement filters suggestions only.
* fix(mobile): honor retired workspace names, on one shared implementation
Mobile hand-duplicated the desktop name-suggestion algorithm and deduped
only against live workspaces, so a phone could still be offered a name
whose deleted workspace left agent conversation state behind at that path.
Both platforms now call one shared selector in src/shared, so the two can
no longer drift. The host publishes retired names as an optional field on
the existing worktree.list response, and mobile fetches them per selected
repo while the create sheet is open — mirroring the desktop hook.
Mobile never calls worktree.list for its catalog (it uses worktree.ps,
which carries rows only), so this is a targeted request rather than a
change to the catalog or its cache. Hosts predating the field omit it and
mobile falls back to live-only dedupe, which is the pre-change behavior.
* fix(worktree): close retirement consistency gaps
* test(worktree): cover retirement runtime contracts
* fix(worktree): retire generated collision names
* fix(worktree): enforce retired names at creation
* refactor(ai-vault): extract the Claude project-dir encoder
The bucket-name encoder and its scope-boundary check were private to the
session scanner, so a second consumer had to reimplement them — and got the
per-character encoding wrong. Move both to a shared module with direct tests.
* fix(worktree): make the retirement seed scan actually match buckets
The bucket encoder collapsed runs of non-alphanumerics while the real one
emits a dash per character, so every dot-path bucket missed and the Windows
default workspace root (C:\...) matched nothing at all. Reuse the shared
encoder and its boundary check, which also stops a repo absorbing a sibling
whose path merely shares its prefix.
Also:
- Derive the workspace leaf by stripping the known encoded parent instead of
guessing from trailing dash segments, which retired the parent directory's
name whenever a workspace was named numerically.
- Reuse isAutoGeneratedCreatureBranchName so the -10 and -100 tiers retire.
- Drop the .codex/sessions root: Codex keeps the cwd inside the transcript
rather than in a directory name, so the scan could only ever see a year
folder. Reading transcript contents is not a trade this feature justifies,
so the gap is documented instead.
- Honor CLAUDE_CONFIG_DIR, which relocates the bucket root.
- Delete the unused retirableLeafName export.
Tests write buckets with the real per-character encoding against a fake home,
covering POSIX, dot-directory, Windows drive and WSL UNC roots; all three
platform cases fail against the previous encoder.
* fix(worktree): retire only generated names, keyed by cwd namespace
Two problems in the host-side registry.
Retirement fired for every create, including names the user typed. The
creature pool contains ordinary words — orca, runner, sole, molly, oscar — so
typing a retired 'nautilus' silently produced directory and branch
'nautilus-2' and burned the name for good. Creates now carry an explicit
nameWasGenerated flag; both the skip and the retire are gated on it, and it
defaults to false so CLI and automation callers are unaffected.
The registry was keyed by repo id, but both readers already discarded the id
and unioned by the cwd collision key, because the collision this prevents is
on the path. Keying by that namespace directly fixes several things at once:
entries no longer orphan when a repo is removed, remove/re-add no longer loses
every retirement for an unchanged path, the missing removeProject prune is
moot, and the backfill promise no longer merges into only the first repo id it
saw. The feature is unreleased, so no migration is needed.
Also:
- Memoize the collision key. It runs computeWorktreePath, which for a WSL repo
is a blocking execFileSync('wsl.exe') whose failure path is uncached, and
the previous code recomputed it once per repo on every create and every
listRetiredNames call.
- Drop retiredNamesByRepo from the worktree list result. It had no readers and
leaked onto 'orca worktree list --json', and its awaited backfill sat on CLI
selector resolution. The dedicated listRetiredNames RPC keeps its consumers.
- Make the three RuntimeStore methods required. RuntimeStore is file-private
with two constructors, so the 'older embedders' the optionality protected do
not exist, and the optional chain silently returned no retirements.
- Revert the unrelated forceDeleteBranch rewrite, and make room under the
file's line budget by extracting the create-args mapping instead.
* fix(worktree): send name provenance and stop gating Create on the fetch
Desktop and mobile now mark a create as generated-name only when the user
typed nothing and the composer fell back to the suggestion, so the host knows
which names it may retire.
Remove the retired-names loading gate from every create path. The host already
skips retired candidates before doing any git work, so the client gate bought
nothing while it could disable Create for the length of a full mobile
reconnect ladder (the wait had no timeout) and blank the desktop button
between queued creates. The suggestion still waits; the button never does.
Also make the web client call worktree.listRetiredNames instead of hardcoding
an empty list — the method is registered and mobile-allowlisted, so the
comment claiming no wire call existed was wrong — and filter the mobile
response to strings so a malformed row cannot throw during normalization.
* fix(worktree): key retirement by repo id and prune it with the repo
Reverts the collision-key storage key. It was a function of workspaceDir,
nestWorkspaces, worktreeBasePath and repo.path, so toggling any one of those
orphaned every retirement for every affected repo at once — trading a rare
churn (remove/re-add) for a common one. The read path already unions by cwd
namespace at query time, so cross-repo sharing never depended on the storage
key.
Instead, address the growth and orphaning directly:
- Drop the registry in removeProject, and in removeProjectForHost once the last
host's copy of the repo id is gone, alongside the sparse-preset deletes that
already follow this convention.
- Bound each repo's registry. The cap sits far above the 552-name pool because
evicting inside it would reissue a name whose agent state is still on disk;
only -2/-3 tier accumulation can ever reach it.
- Carry retirements through profile transfer, re-keyed to the destination repo
id and dropped from the source, mirroring sparsePresetsByRepo.
Separately, fix the backfill merge: the scan promise is cached per cwd
namespace, but it closed over the first repo id that triggered it, so a second
repo in the same namespace received nothing. The scan stays shared; the merge
moves out of the cached promise and runs for whichever repo asked.
Local repos re-seed on re-add through that backfill. SSH repos do not — the
scan cannot see the execution host — which is now stated in the module.
* docs(worktree): spell out why the retirement bound sits above the pool
Names the trap directly: the neighbouring 50/200 bounds cap histories, so
lowering this one to match them would silently start reissuing names whose
agent state is still on disk. Also states that oldest-first eviction is a
deliberate least-bad choice rather than a neutral one.
* fix(worktree): send name provenance from the web runtime client
This client hand-enumerates worktree.create params, so the new optional field
was silently dropped and typecheck could not see it. On web and paired-desktop
the host therefore never received it: generated names were never retired, and
the host-side skip that backstops a stale suggestion was disabled too. The same
client does fetch retired names for suggestions, so it was filtering against a
registry nothing ever wrote to.
The test asserts both directions, and fails without the fix.
* fix(worktree): retire names that took more than one collision suffix
isAutoGeneratedCreatureBranchName strips exactly one trailing -N, which is
right for auto-rename eligibility but wrong here. Once the pool is spent the
suggester emits nautilus-2, and a collision on that yields nautilus-2-3 —
which a single strip leaves as nautilus-2, not a pool name, so retirement
no-opped at exactly the tier where every base name is already gone. Strip
repeated suffixes locally rather than moving the auto-rename predicate.
* perf(worktree): keep the retirement backfill off the blocking WSL probe
The backfill runs on composer repo-select, not just at create time, and it
derived the probe path synchronously — which for a WSL repo with a mirrored
workspace dir reaches getWslHome and its blocking execFileSync('wsl.exe').
A stopped distro froze the main process for up to 5s on composer open.
Adds an async twin of computeWorktreePath and uses it for the probe. Resolving
the home there also warms the shared cache, so later sync callers are free.
Also stops memoizing the collision key when the WSL home is still unresolved:
only the success path is cached upstream, so caching the fallback namespace
would strand the repo there for the rest of the session.
* fix(worktree): hold retired names across a refresh instead of blanking
refreshKey changes on every workspace-list mutation, so create-multiple
refetches after each create and the hook returned an empty list until the
refetch landed — precisely the window in which resetForNextCreate clears the
name field and a fresh suggestion is drawn. Keep the previous answer while
revalidating and reset only when the repo changes; a failed refresh keeps what
was already loaded rather than un-retiring everything.
Also makes the returned array referentially stable, so the suggestion memo
downstream stops rerunning on every refetch.
* refactor(worktree): put the retired-name cache rules on one implementation
The desktop and mobile hooks that fetch retired names had already drifted
four ways. The transports genuinely differ (IPC vs RPC), but the caching
rules must not, and mobile's copy reset to [] on any error -- which
un-retires every name for the rest of the sheet session, the one outcome
retirement exists to prevent.
Moves the rules into src/shared/worktree/retired-name-cache: response
normalization, the never-leak-across-repos rule, and the hold-previous-on-
failure rule. Pure, no React, because src/shared is on the main process's
import graph. Each platform keeps its own transport and effect.
Mobile moves up to desktop's behavior: it now holds the previous answer
through a failed refresh, and refetches when the workspace list changes
instead of never refetching after mount.
Also drops the unused `loading` return. Neither platform consumed it; its
only consumer was the Create-button gate reviewed out earlier, and removing
it makes that regression unexpressible.
* fix(worktree): import shared types from their real modules
Main dropped the src/shared/types barrel, so the retirement module's import
resolved locally but not against the PR's merge base.
* refactor(worktree): bound the retirement registry by tier compaction, not eviction
Retirement is a correctness guarantee — a spent name's directory may still hold
agent conversation state keyed by that cwd — so the 2000-entry cap was the wrong
shape: reaching it handed a name back. At the owner's measured rate (~6.6 pool
names retired per day in one repo) the cap was ~9 months out.
Names come from a fixed 552-entry pool and the suggester only reaches tier N+1
once every tier-N name is taken, so a completed tier is exactly a set that no
longer needs listing. A row is now a watermark plus the names above it: reads
answer at-or-below the watermark with no lookup, and compaction drops the 552
entries the watermark now covers. Bounded at one pool per repo forever, with no
eviction and nothing un-retired.
Tiers can complete out of order (a create-time collision can spend `nautilus-2`
while tier 1 is open), so compaction loops and higher-tier names simply wait.
The RPC result carries the watermark beside the names as a new field; a client
predating it reads the names only and under-retires the compacted tiers, which
degrades to the pre-retirement behavior rather than breaking.
* fix(worktree): preserve generated name retirement across failures
|
||
|
|
c0f9dcc8f4 |
fix(git): allow bounded override of worktree-add timeout (#12823)
* fix(git): allow bounded override of worktree-add timeout Keep the 180s OneDrive stall guard as the default floor, but accept ORCA_WORKTREE_ADD_TIMEOUT_MS up to 30 minutes for legitimately slow checkouts (large repos, git-crypt). Preserves a closed upper bound; never removes the timeout. Fixes #12696 * review: read the worktree-add timeout override at the call site Keeps WORKTREE_ADD_TIMEOUT_MS meaning the 180s default instead of silently becoming an env-resolved value, drops the redundant third export, and folds three parse guards into the clamp Number() already covers. Adds the missing coverage that addWorktree actually passes the raised timeout to git — reverting the call-site wiring previously failed no test. * review: clamp an infinite override to the max and warn on a discarded value Number.isFinite sent ORCA_WORKTREE_ADD_TIMEOUT_MS=Infinity — the natural way to say 'stop killing my checkout' — back to the 180s default, handing the operator the exact failure they set the variable to escape. Reject only NaN and let the clamp handle magnitude. Every discarded or clamped value was silent, so the '=300' seconds/ms mixup the floor exists for produced an identical 'git timed out.' with no signal. Warn once, naming the accepted range. Also pins both bounds as literals and refreshes two comments that no longer described the code. * review: name the real problem in the override warning An unparseable value took the range branch, so ORCA_WORKTREE_ADD_TIMEOUT_MS=600_000 — the literal style this file itself uses — reported a bound violation that had not happened. Split the two cases and quote the value so trailing whitespace is legible. Uses the file's [git/worktree] log prefix, drops a #7225 citation that describes a startup/UI-freeze report rather than a large checkout, and states why the ceiling is 30 minutes. * review: give the resolver a contract and stop splitting the timeout block Moves resolveWorktreeAddTimeoutMs below the constants so the module's five timeouts read as one group, and replaces the edge-case JSDoc with the actual contract — what it reads, what range it clamps to, when it warns. Comments the NaN-comparison the unparseable-value warning depends on, since 'fixing' it with an isNaN guard would silently delete that warning. Test spy now matches the file's local-spy idiom, the bound literals get their own test, and the env stub deletes the key instead of setting an empty string. * review: pin the clamp-up warning text Every warn assertion covered a value clamped DOWN to the floor, so swapping the discriminator back to !Number.isFinite passed all 74 tests while telling an operator that ORCA_WORKTREE_ADD_TIMEOUT_MS=Infinity 'is not a number; using 1800000ms' — naming the number it just used, and misdirecting exactly the person this override exists for. That mutation now fails. Restores the STA-1292 rationale the call-site comment had dropped, and puts the env var name and issue back on the ceiling constant. * review: correct two comment claims about the warn path The JSDoc promised a warning 'whenever the value is not used verbatim', but trimming and fractional truncation deliberately stay silent — the suite asserts exactly that for '300000.9', so the contract contradicted the tests below it. The condition comment named 'NaN !== NaN', a comparison that never runs: resolved is the default whenever requested is NaN, so the live comparison is 180000 !== NaN. Same warning, right mechanism. * review: fix the ceiling arithmetic and name the default/floor coupling 30 min against a 3.5 min worst case is ~8x, not ~10x — the comment's only job is justifying that number. States the actual cost too: a genuine stall now blocks a create for up to 30 min instead of 3. WORKTREE_ADD_TIMEOUT_MS silently serves as both the default and the clamp floor, so tightening it to fail faster would also re-admit the '=300 means seconds' mistake the floor exists to catch. Now said out loud. Widens 'git-crypt' to 'a slow content filter' so an LFS or large-monorepo reader does not conclude their case is different, drops a call-site clause that restated the constant's comment, and corrects a test comment that claimed an idiom the code does not use. * review: pin the warning's prefix and variable name Deleting the [git/worktree] prefix left the suite green — all three warn assertions started matching after it. A diagnostic nobody can grep for is not a diagnostic, so one assertion now pins the whole line. * review: correct the last three comment claims A blank value is clamped (Number('') is 0) and stays silent, so 'warns when a value is rejected or clamped' had an exception the test below it already exercised. Now says non-blank. The floor-coupling note claimed lowering the default re-admits the '=300 means seconds' mistake; it does not — a 60s floor still clamps 300. The actual cost is that the minimum any override can request drops with it. Drops the spy comment rather than rewriting it a third time; beforeEach and afterEach say it themselves. --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> |
||
|
|
77f23b013f |
refactor(shared): drop the shared/types barrel and import from the real modules (#14447)
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as a re-export barrel so the import sites did not have to change. This removes the barrel: every consumer now imports from the module that actually declares the type, and `src/shared/types.ts` is deleted. Barrels hide where a type lives, make every consumer look like it depends on the whole domain, and let an unrelated edit invalidate a module that ~2,000 files transitively import. 2,323 import declarations across 2,321 files. Rewritten mechanically: each specifier was resolved to an absolute path via the TypeScript AST and recomputed, rather than string-substituted, so alias forms (`@/../../shared/ types`) and per-specifier `type` modifiers survive. Four cases the mechanical pass had to handle, each found by a gate rather than by reading the diff: - Modules inside `src/shared` import the barrel as `./types`, not `shared/types`. A pre-filter on the latter string skipped 176 of them and left imports dangling at a deleted file, which surfaced as confusing `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>` errors rather than "module not found". - The barrel RENAMED one type on the way through (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name in the owning module has to be re-aliased at each consumer. - Three test files put `;(globalThis as ...)` on the line after the import. TypeScript parses that `;` as the import statement's terminator, so replacing through `statement.getEnd()` deletes it and breaks ASI. The rewrite now stops at the module specifier. - A file that already imported directly from a module got a SECOND import from it, because the barrel re-exported those same names — which trips `import/no-duplicates` under `--deny-warnings`. A post-pass merges declarations sharing a specifier and type-only-ness; the `import type` plus `import` pair from one module is left alone, since that form is allowed. Splitting one barrel import into several genuinely adds lines, which pushed `terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character import must wrap, and neither local type collapses onto one line (101 and 116 characters). Rather than contort a type declaration to fit a line budget, `collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` — they are pure structural operations on the layout tree and independent of PTY ownership. `visible-worktrees.ts` similarly loses its own mini-barrel re-export of `isDefaultBranchWorkspace`, with the four real consumers repointed at the declaring module. No `max-lines` bypass added. Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted first — these projects are `composite: true` and reuse stale caches); the full `pnpm lint` green, not just bare oxlint — the narrower local check is what let the duplicate imports reach CI; max-lines ratchet OK at 344. |
||
|
|
583ab1601b |
refactor(shared): group worktree, github, and linear modules into folders (#14437)
`src/shared` is a flat directory of ~1,150 entries. The worktree, github, and
linear domains accounted for 71 of them, so finding the module you wanted meant
scanning a wall of same-prefixed filenames.
Move each domain into its own folder and drop the now-redundant prefix:
src/shared/github-pr-types.ts -> src/shared/github/pull-request-types.ts
src/shared/worktree-id.ts -> src/shared/worktree/id.ts
src/shared/linear-links.ts -> src/shared/linear/links.ts
This follows the existing `network/` and `new-workspace/` convention in the
same directory, which also drop the prefix inside the folder.
Whole clusters move, including tests. Foldering only part of a domain would be
worse than flat: a reader would have to check both `github/` and the flat
directory, and `github-auth-types.ts` / `github-project-types.ts` are type
modules that belong with the rest. No files with these prefixes remain flat.
Import specifiers were rewritten by resolving each one to an absolute path and
recomputing it, not by string substitution, so the `@/../../shared/...` alias
forms are handled correctly. 501 specifiers across 298 files.
Two things `tsc` cannot catch, handled explicitly:
- `github-project-types.ts` carries its own `max-lines` bypass, so its baseline
entry is REPOINTED to the new path rather than pruned. Pruning would drop the
bypass and then flag the new path as a fresh violation. Ratchet stays at 345.
- `mobile/` is outside `pnpm typecheck` and cannot be typechecked here
(`mobile/node_modules` is empty). Instead every relative specifier in the repo
was resolved against the filesystem: 174 unresolved before this change and 174
after — identical, so nothing broke in mobile either.
The pinned `tests/e2e/.cross-version-checkouts` fixtures are deliberately NOT
rewritten; they are a snapshot of an older release and still reference the old
paths.
Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches).
|
||
|
|
991a3fe963 |
chore(lint): update oxlint to 1.77 and enable no-op cleanup rules (#13901)
Enable eleven oxlint rules that simplify code without changing behavior, and fix
every existing violation. Each candidate was gated on measured cost rather than
assumption, so rules that regressed runtime performance or type checking were
dropped instead of suppressed.
typescript/no-redundant-type-constituents is the largest addition: 113 sites, no
autofix. Dead constituents are deleted. Where the redundant literal existed to
document intent (`string | 'all'`), it is preserved as `(string & {})`, which
keeps the autocomplete hint the original code was reaching for instead of
flattening it away. The rule also caught a broken import —
remote-shared-control-retirement-probe.ts pulled RuntimeStatus from
src/shared/types, which does not export it, so the type silently degraded to
`any`; no tsconfig covers that file, so tsc never saw it.
oxlint stays at 1.77.0 rather than 1.78.0 because .npmrc sets
minimum-release-age=4320 and 1.78.0 is younger than that window.
Rules evaluated and rejected, with what disqualified each:
- prefer-string-raw: String.raw is a runtime call, not a literal (184x slower)
- prefer-string-replace-all: 26% slower
- text-encoding-identifier-case: ~5% slower, reproducible
- prefer-spread: [...str] is 110% slower than split('') and differs on surrogates
- no-implicit-coercion: `!!x` narrows types and `Boolean(x)` does not (22 tsc errors)
- prefer-arrow-callback: arrows are not constructible, breaking `new` on mocks
- object-shorthand: rewrites source text asserted by a tracked reliability gate
- switch-case-braces: pushes ten files past max-lines, which cannot be suppressed
- no-useless-switch-case: drops `case undefined:` that switch-exhaustiveness-check needs
- arrow-body-style: 115 violations have no fix, and it breaks max-lines
- newline-after-import: false-positives on the leading-semicolon ASI idiom
electron-vite-output-contract asserted on the literal
Object.prototype.hasOwnProperty.call text; retarget it to Object.hasOwn, which
rejects inherited keys identically.
|
||
|
|
e790266546 | fix(windows): show first window before shell PATH hydration (#13799) | ||
|
|
c1e75477f3 |
Fix static analysis page stuck in loading state (#13674)
* Fix static analysis page stuck in loading state - Bound check-details requests with 30s timeout, matching remote RPC budget - Track request IDs to discard stale responses when context changes - Propagate githubRepository through store and components for proper routing - Add retry button for failed check-details loads - Improve accessibility with ARIA labels for loading and error states * Fix static analysis page stuck in loading state When an open check-details tab's repository is removed, the loading state would continue indefinitely because the fetch was still being triggered. Prevent the fetch call in this scenario to unblock the UI. Also migrates translation keys to obfuscated identifiers. * Fix static analysis page stuck in loading state Add deadline-based timeouts and request ID tracking to prevent stale responses from freezing the checks panel. Include abort signal propagation throughout the request chain and provide retry UI for failed check details loads. * fix(checks): prevent loading state from getting stuck on retry - Consolidate mount checks into a helper function - Details now clear when a new request begins - Add i18n strings for retry status |
||
|
|
9deb72f9ed |
fix(git): support Windows-linked worktrees in WSL projects (#13483)
* fix(git): support Windows-linked worktrees in WSL projects * fix(git): harden WSL linked worktree routing * fix(test): defer WSL routing filesystem access * test(git): type WSL routing probe mock * fix(git): retry transient WSL route probes safely --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
5538584c74 | perf(git): overlap status with conflict detection (#13529) | ||
|
|
73efab98f7 | perf(orchestration): keep drift Git off main thread (#13440) | ||
|
|
69ca0154b6 | fix(git): bypass WSL login shells for status reads (#13207) | ||
|
|
46b9d3b13a |
Break out test and generated lines in branch line total (#13057)
* rm comments * reduce comment |
||
|
|
debf4affe7 |
Display total lines of code change in branch header (#12771)
* Add branch line total chip to source control header Display the total lines added and removed across a branch from its fork point, measured via `git diff <mergeBase>`. Only computed when the chip is visible (request gate on merge base OID), with 500ms soft deadline to protect status latency and 15s hard timeout. Deduplicated across concurrent pollers and cached alongside line stats. Omitted on failure — always shows exact or nothing, never a partial estimate. Updates throughout the stack: native git status, relay, renderer store/API, and UI components. * Pin branch line total to app locale Format line counts using the app's configured locale instead of the system locale, ensuring consistent cross-platform display and test reliability. * test: wait for coalescer joins instead of fixed sleep Hold the diff until the second status pass actually takes the branch-total coalescer lease instead of using a fixed 400ms sleep. Fixes timing-dependent flakiness on slow machines. |
||
|
|
de64337c26 |
fix(worktree-watcher): refresh status after external pushes (#12361)
* fix(worktree-watcher): surface external push -u through the git-common watch An external-shell 'git push -u' writes only the common .git/config (plus refs/remotes/<remote>/<branch>), both invisible to the git-common event filter, so the Checks panel stayed on 'No upstream configured' until the renderer safety poll. Classify the common config and remote-tracking refs as status-tier signals, poll config alongside the other primary-checkout metadata files, and keep FETCH_HEAD/reflog/ref-lock churn ignored. * fix(worktree-watcher): refresh after subsequent pushes |
||
|
|
9deee5ad2f |
perf(worktrees): delete worktree directories after the removal returns (#12416)
* perf(worktrees): delete worktree directories after the removal returns `git worktree remove` deleted the whole checkout inline, so the remove IPC held the watcher/PTY gate for the entire recursive delete (prod traces: worktree.remove.git_remove p50 8-14s, p90 29s, max 34.7s). Local removals now rename the checkout into a hidden sibling trash root, clear Git's registration for the missing path, and delete the moved tree in the background. Renames that cannot run (WSL, other volume, Windows open handles) fall back to the previous in-place removal unchanged. * test(worktrees): keep no empty trash root when the rename cannot run * fix(worktrees): harden deferred trash cleanup * fix(worktrees): keep WSL trash on its owning host |
||
|
|
73c5009b82 |
chore(dead-code): drop ~2k lines of unreachable exports and orphan modules (#12077)
* chore(dead-code): drop 2k lines of unreachable exports and orphan modules Ran knip across every build entry (main, preload, renderer, popout, web, cli, relay, workers, forked sidecars, config scripts) and removed what no entry graph can reach. - 11 orphan modules nothing imported, plus one test that only covered them - 159 unused exports/types, with their now-dead helpers, imports and tests Each candidate was verified against dynamic references before deletion. 42 knip hits were false positives and are kept: shared modules consumed by the mobile/ workspace, the src/shared/plugins/** public API, vendored shadcn primitives, and relay wire-protocol constants held for compatibility. Adds knip.json + `pnpm audit:dead-code` so this stays measurable. Verified: pnpm typecheck, pnpm lint, and 2081 tests across the 73 affected test files all pass. * chore(dead-code): move knip config under config/ Root-level additions are blocked by the root directory guard. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
1562f12f78 |
fix(P1-D): coalesce remote-ref probes, TTL negatives, and bound unsettled keys (#12065)
* fix(P1-D): coalesce remote-ref probes, TTL negatives, and bound unsettled keys Keep forge resolution from stampeding git under worktree fan-out, let remotes added mid-session be discovered without a restart, and refuse pathological new-branch waves once the unsettled map is full. * fix(P1-D): stop abandoned probes publishing, and split capacity refusals A coalesced probe abandoned as stale kept running and still wrote its answer to the cache, so a late permanent miss could land over the successor's fresher one. Probes now publish only while they still own the in-flight key. The hosted-review capacity refusal told brand-new branches that an earlier attempt of their own never answered when the refusal was really the unsettled map or the process-wide detached cap; each cap now says what it is. Also caches stable "no such remote" SSH misses under the negative TTL instead of re-spawning the probe on every poll. Co-authored-by: Orca <help@stably.ai> * Bound SSH remote URL probe with deadline to prevent hangs The SSH branch of remote URL probes was unbounded — the relay's bounds are per-phase and reset on every frame, so a relay dribbling output would outlive them. Pass AbortSignal.timeout to the SSH provider's exec call to enforce the same 30s deadline as local probes. Treat AbortError as a transient probe error: it signals unavailable infrastructure (deadline or cancellation), not a negative answer about the remote. --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
ced4a2a959 |
fix(P1-D): bound hosted-review in-flight lookups so a wedged provider cannot pin a branch (#12030)
* fix(P1-D): bound hosted-review lookups with a detachable deadline The `inflight` map in the hosted-review branch cache was only ever cleared when the lookup settled, and nothing bounded how long that took. One wedged provider call pinned its branch for the life of the process: every later poll joined the same dead promise, so the card loaded forever with no in-session recovery. Each lookup now runs under a 120s deadline. Nothing below the funnel can be cancelled, so the deadline detaches instead: the record is released, the callers get the last known review (or a timeout error), and the branch enters the existing failure backoff. The lookup keeps running and its answer is still adopted if it lands, so a slow-but-alive host converges rather than failing forever. A token identity keeps a detached lookup from evicting the record that replaced it, and a wall-clock sweep expires records whose timer never fired — main's timers are suspended across system sleep. `inflight` is capped independently of the completed cache. The failure backoff moves to its own module: it has a different lifetime from the answer cache and is what a deadline records against. * fix(P1-D): bound `git remote get-url` on the local/WSL path `getRemoteUrlForRepo` ran the git child with no timeout, which is the one unbounded step under the hosted-review lookup funnel: `git/runner.ts` only arms its kill path when a timeout is passed, so a dead network mount or a stalled WSL interop hangs the call and everything above it. The SSH branch is already bounded by the relay mux's 30s request timeout, so it is unchanged. * rm review doc * rm review doc * test(P1-D): add probe tests and transient-failure recovery verification Add tests for coalesced-probe and remote-url-probe infrastructure. Add integration test verifying that transient Bitbucket API failures don't cache as a definitive no-review result, allowing recovery after cache TTL expiration. * fix(P1-D): track lookups from start, prevent stale scope adoption - Count unsettled lookups when they start, not after deadline expires: prevents multiple concurrent lookups for the same branch. - Add evicted generation floor: prevents adopting stale results when scope is invalidated and evicted from the map. - Consolidate duplicate repository reference cache logic into createRemoteRefProbeCache utility. - Fix deadline wrapper in git config signature lookup: bound the caller's deadline only, not the coalesced probe itself. * feat(P1-D): add remote-ref-probe-cache utility Cache successful remote URL probes per repo/runtime to avoid duplicate work. Skip caching transient errors and SSH failures so providers can retry on reconnect, preventing stale scope adoption during the session. |
||
|
|
6e2a88c091 | perf(worktrees): avoid redundant fetch during deletion (#11918) | ||
|
|
05206046f6 |
chore: condense code comments (#12008)
* chore: condense code comments * chore: shorten more code comments * clarify PTY agent session descendant cleanup behavior Refine the comment on ptyAgentSessionIds to more accurately describe when agent sessions sweep their descendant process trees and note the exception on immediate Windows shutdown. |
||
|
|
d5c4d953ec | perf: coalesce cancellable git status reads (#11691) |