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.
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.
* 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.
* 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.
* 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>