Skip to content

fix(git): scope capability state to the distro that runs git - #15297

Open
OrcaWin wants to merge 1 commit into
mainfrom
OrcaWin/git-execution-owner
Open

OrcaWin wants to merge 1 commit into
mainfrom
OrcaWin/git-execution-owner

Conversation

@OrcaWin

@OrcaWin OrcaWin commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​258 $\color{#cf222e}{\Huge{\mathbf{−}}}$​2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​256
Prod 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​93 $\color{#cf222e}{\Huge{\mathbf{−}}}$​35 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​58

ELI5

Orca can run git in three different places: on Windows directly, inside a WSL distro, or over SSH. Before running a command it has to answer "which one?", and it also remembers what each host's git can do (which flags it supports) so it doesn't re-check every time.

The problem: it answered that question in several places, and they could disagree. If a call pointed at a folder inside distro A but also carried a leftover hint saying B, git ran in A — but everything Orca learned about git got written down under B. Later calls that really were in B then trusted notes taken on a completely different machine.

This makes one function the answer to "which host?", and points the runner, the notes, and the output translation at it.

Root cause

Independent derivations of the execution host, with two different precedence orders:

site precedence used for
runner.ts:wslDistroForCommand cwd ?? hint picking the direct-read environment
runner.ts:resolveCommand cwd ?? hint routing (wsl.exe -d, guest cwd)
runner.ts:translateWslOutputPaths cwd ?? hint rewriting guest paths in git output
git-capability-state.ts:getLocalGitExecutionHostKey hint ?? cwd capability cache key

Routing follows the cwd, because resolveCommand reads parseWslPath(cwd) before it looks at the override. Capability state preferred the override. They agree in every case except a cwd and hint naming different distros — and there they disagree silently, which is the shape AGENTS.md calls out:

Scope capability state to the host that executes Git: native, WSL distro, SSH provider, or relay connection.

A capability probed against the distro in the cwd was filed under the hinted one, so a later call genuinely on the hinted distro reused a result from a host that never ran the probe. Git versions differ per distro (docs/reference/git-compatibility.md), so this can hand a caller a "supported" verdict for a flag its actual git does not have.

Fix

git-execution-host.ts owns the derivation. Precedence follows execution: the cwd wins; the hint applies only when the cwd cannot name a distro. All four sites above now go through it.

One route overrides the name of the host and cannot be decided synchronously: a prepared Windows-authored linked worktree runs native git.exe even under a WSL hint (usesHostGitForWslLinkedWorktree). That answer is now passed in as usesHostGit and resolved inside the owner, rather than being compensated for by the caller stripping the hint before calling in. The module docblock says explicitly that it reports the host a target names, and points at the probe that overrides it.

Three behavior changes ride along, each with a mutation-checked test:

1. Capability keying for a mismatched cwd + hint. The original fix. Reverting the precedence fails exactly four tests (two key-shape, two runner-level argv) and nothing else.

2. The shell-free WSL read route no longer requires the caller to repeat what the cwd already says. shouldAttemptWslDirectGit ended in && options.wslDistro, so a read with a WSL UNC cwd and no hint fell back to bash -c — and an explicit preferWslDirectGit: true was ignored outright on that path. There was a test pinning that (leaves override-less WSL UNC routing on its existing non-login shell); it pinned the defect and is now flipped, with a comment saying why. The distro was always available: wslDistroForCommand(cwd, hint) on the very next line already returned it.

3. The capability key folds distro case. Windows treats \\wsl$\ubuntu and \\wsl.localhost\Ubuntu as one host (foldWslUncPathCaseInsensitiveParts in src/shared/wsl-paths.ts says so). Before, the key interpolated the raw string, so two spellings of one distro got two GitCapabilityCache instances, each independently probing rev-parse --path-format, worktree list -z, merge-tree --write-tree and for-each-ref --exclude and eating the failure before falling back. That is duplicated wsl.exe spawns on exactly the path the sibling probe-storm work (#15293) is trying to quiet. GitExecutionHost.distro stays verbatim, because that is what wsl.exe -d receives.

What this does NOT change (deliberate)

runner-wsl-execution-mode.test.ts characterizes a remaining defect without fixing it: a write still picks its shell from whether wslDistro was populated, not from the host and command class.

NO wslDistro : ["-d","Ubuntu","--exec","bash","-c", "cd '/home/user/repo' && ... git commit -m x"]
WITH wslDistro: ["-d","Ubuntu","--exec","sh","-lc", "...getent passwd... exec $shell -ilc ..."]

The distro is Ubuntu in both — derived from the cwd. So wslDistro carries no routing information here; its only effect is to flip login shell vs bare bash -c. A write on the no-hint path therefore runs with no ~/.profile, no user PATH, and no ssh-agent, which an authenticated push needs.

The test uses a write deliberately. An earlier draft asserted this with rev-parse, which is now an always-read command: once the WSL read-environment probe resolves, a read takes the shell-free route and the sh -lc assertion stops holding. That version only passed because beforeEach reset the probe cache, so it pinned a cold-start artifact. A write can never take the read route, so the assertion is cache-independent — and it is the case the rationale is actually about.

Fixing it is not in scope here because flipping it decides whether every WSL write gets the user's PATH and ssh-agent. That needs its own PR and real-Windows evidence.

Relationship to #15039 / #15040 / #15060 / #15257

All of these have now merged. An earlier revision of this description described them as open and sequenced work behind them; that was written against a base from before they landed and was wrong.

PR state note
#15039 MERGED 6e8da1df8d9
#15040 CLOSED content landed as #15290, 3a9f40ed709
#15060 MERGED b7f2e177121
#15257 MERGED 6efd4061dc4

#15257 auto-classified reads (options.preferWslDirectGit || isWslDirectGitReadCommand(args)) but left the trailing && options.wslDistro, so it moved the command-class decision off the caller while leaving the host decision on the caller. That residue is what change 2 above removes. Collapsing the write-side shell choice onto (host, command class) is unblocked today — it is not waiting on anything.

Testing

Rebased onto 1be6a035b9e and re-verified there:

  • src/main/git + src/main/github: 1578 passed, 4 skipped, 0 failed (145 files).
  • pnpm run typecheck exit 0 · pnpm exec oxlint exit 0 · audit:code-quality:native exit 0 · audit:code-quality:type-aware exit 0 · check:reliability-gates OK (85 gates) · check:max-lines-ratchet OK (188 grandfathered, no new bypasses) · oxfmt --check clean.
  • Whole-repo src/main + src/shared on this macOS machine: 13 failures across 10 files, none in src/main/git. Re-running those 10 files directly narrowed it to 9 failures in 6 files; the other 4 files pass in isolation and fail only under full-suite load. Those same 9 tests fail identically, by name, on a clean origin/main worktree run side by side. All are timing-sensitive (fs watchers, SFTP abort races, retention caps) and pre-existing.

Mutation checks (each reverts one part of the fix and re-runs all 84 src/main/git files):

mutation fails
precedence back to hint ?? cwd lets the cwd win over a hint that names a different distro, files state against the distro in the cwd, not a hint naming another one, runs a read in the cwd distro, with that distro environment, despite a conflicting hint, runs a write in the cwd distro despite a conflicting hint
restore && options.wslDistro takes the shell-free route for an override-less WSL UNC cwd, gives a read the same shell-free route with or without the hint
drop .toLowerCase() from the key keys both UNC spellings of one distro together while passing it through verbatim, shares one cache across both UNC spellings of a distro

Limitations

All verification was on macOS. There is no Windows or WSL host in this environment. Every WSL claim here is from code reading and from tests that force process.platform = 'win32' and mock child_process. An earlier revision of this description quoted real wsl.exe measurements (git 2.43.0 in 82ms, a sudo_root banner on the login-shell route, absent-distro fast-fail); those were not reproducible here and are not repeated as evidence.

The mismatched cwd+hint scenario has not been observed in the field. It was confirmed reachable by reading project-runtime-git-options.ts and local-project-runtime-resolution.ts, not from a crash report.

Change 2 is a real behavior change on Windows that no Windows machine has run. Reads with a WSL UNC cwd and no wslDistro now take the shell-free /usr/bin/env … git route instead of bash -c, once the read-environment probe has resolved for that distro. It degrades safely by construction — a non-zero exit from the direct route re-runs through resolveGitCommandWithoutProbe and can disable the route for that distro (invalidateMissingDirectWslGit / disableDirectWslGitAfterSuccessfulFallback) — but the first-run path is untested on real hardware.

Not every derivation of the git execution host is unified, despite this PR's framing. Four sites in src/main/git are. At least three more survive and were deliberately left alone:

  • src/main/runtime/orca-runtime.ts:getCanonicalFetchKey keys wsl:${gitOptions.wslDistro} : 'local' — hint-only, same key vocabulary — and then runs gitExecFileAsync cwd-first. A UNC repoPath with no hint is filed under local. This is a live latent mis-keying of the in-flight-fetch dedup key, out of this diff.
  • src/main/github/conflict-summary-cache.ts:getConflictSummaryGitRuntimeKey returns wsl:${wslDistro} ignoring cwd. Safe today only because repoPath is also part of the composite key and therefore pins the host.
  • src/main/gitlab/gitlab-known-host-probe.ts is hint-only; it has no cwd, so it is defensible.

translateWslOutputPaths keeps its own win32 gate rather than relying on the owner's platform-agnostic answer. Off Windows a literal //wsl$/x directory is an ordinary path whose git output must not be rewritten. The gate is now expressed by passing cwd: undefined off win32, so the derivation is still shared, but the platform knowledge is still duplicated at the call site.

gitExecutionHostForTarget is not platform-gated. On Linux/macOS a directory literally named //wsl$/X/... keys as wsl:X rather than local. This matches the key function it replaced exactly, and is unreachable via the project-runtime hint (resolveProjectExecutionRuntime returns host for appPlatform !== 'win32'), but the key is still not strictly "the host that executes git" off Windows.

No capability state is persisted anywhere, so no migration is needed or missing. GitCapabilityCache is three in-memory maps and localCapabilitiesByExecutionHost is a module-level Map; a restart starts empty. Both the precedence change and the case fold therefore only affect keys within a single process run.

No wire, RPC or IPC surface changed. All touched files are under src/main/git. The relay's per-handler GitCapabilityCache (src/relay/git-handler.ts) and the SSH-provider-keyed cache are untouched, and neither is WSL-aware, so a mixed-version client/host pair sees nothing new.

Key collision was checked and is impossible for reachable shapes. local can never equal wsl:${distro}; a distro literally named local keys as wsl:local. Nothing routes SSH- or relay-executed git through getLocalGitCapabilityCache.

CI needed one re-run for an unrelated flake, and hit a different one before that. On the pre-rebase head, shard tests node 24 4/16 failed in src/main/native-chat/transcript-watch-liveness.test.ts:181 — an fs-watcher timing test with no git dependency and a prior deflake commit (#14335). On the first post-rebase run, shard 2/16 failed in src/main/browser/browser-cookie-import-partition.electron.test.ts:188 with XIO: fatal IO error 2 ... on X server ":99" and a dbus connect failure, i.e. the Xvfb display died under the Electron fixture. Neither file imports anything from src/main/git. Re-running the failed jobs turned the run green with no code change, which is what a flake looks like — but it also means these two shards have passed once each rather than repeatedly.

Adversarial review

Three independent adversarial reviewers went over this branch before merge. Lenses: (a) does the new abstraction actually own what it claims to own, and what still bypasses it; (b) do the added tests discriminate — mutation-testing each assertion rather than trusting a green run; (c) mergeability, cross-platform reachability, cache-key collision/fragmentation, and whether the PR description matches the code and the current state of main.

They returned 11 blocking findings (3 major, 8 minor). All were fixed; none were rejected. The notable ones:

  • The branch did not merge. mergeable=CONFLICTING. It was based on a merge-base from before fix(git): fence buffered WSL login-shell reads #15060 and fix(git): run WSL git reads without a shell #15257 landed, and the import block collided. Resolving it by taking the branch's whole runner.ts silently reverts fix(git): run WSL git reads without a shell #15257 and fails 12 tests. Fixed by rebasing onto origin/main, keeping both imports.
  • The abstraction was built and then not applied where it would have mattered. shouldAttemptWslDirectGit still ended in && options.wslDistro, so the hint-less WSL callers the description named as the victims were still excluded by the raw optional field. One reviewer demonstrated it: same host, same argv, same distro, shell-free vs bash -c decided purely by the presence of an optional field. Fixed (change 2), and the test that pinned the old behavior was flipped.
  • The description was materially stale. It listed fix(git): fence buffered WSL login-shell reads #15060 and fix(git): run WSL git reads without a shell #15257 as open and sequenced follow-up work behind them; both had merged. A reviewer would have concluded the opt-in root cause was handled elsewhere and stopped looking. Rewritten above.
  • The runner-level tests did not pin the fix's load-bearing premise. The added routing test compared {cwd: Ubuntu} against {cwd: Ubuntu, wslDistro: 'Ubuntu'} — matching distros — so it passed unchanged under a hint-first mutation. Replaced with an assertion that seeds distinguishable read environments for Ubuntu and Debian and checks both the -d flag and the guest gitPath/PATH for a deliberately mismatched hint.
  • The characterization test pinned a cold-cache artifact. It used rev-parse, which after fix(git): run WSL git reads without a shell #15257 takes the shell-free route once the probe resolves; it only passed because beforeEach reset the cache. Switched to a write.
  • The key fragmented one host into two caches. Raw distro interpolation, while Windows folds distro case. Fixed (change 3).
  • The "single owner" did not model the linked-worktree host-git route, and its docblock contradicted itself ("actually runs git" vs "the host a target names"). Fixed by moving the override inside as usesHostGit and rewriting the docblock.
  • resolveCommand and translateWslOutputPaths still derived the host themselves. Both migrated; GitExecutionHost now carries the parsed guest path so resolveCommand can consume it.

@coderabbitai

coderabbitai Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds shared Git execution-host classification for native and WSL targets. WSL distro detection prioritizes the working directory over a conflicting distro hint. Capability-state keys now use the shared host identity. Git command routing also uses the shared classification. Tests cover host keys, precedence, distro routing, and WSL shell modes.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description thoroughly explains the problem, implementation, testing, limitations, and scope, but it omits the required linked issue and visual-proof sections. Add the required Linked Issue section with an issue reference and include Visual Proof, or state why visual proof is not applicable.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: scoping Git capability state to the distro that executes Git.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/git/runner.ts (1)

417-418: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one resolver for command host selection.

Line 417 uses the shared resolver, but resolveCommand still selects its own WSL distro at Lines 323-325. Derive the routed distro from gitExecutionHostForTarget there too. Retain parseWslPath only to obtain linuxPath. Add a runner assertion with an Ubuntu UNC cwd and a conflicting Debian hint.

As per coding guidelines, “scope capability state to the executing host and test fallbacks, caching, concurrent probes, and host isolation.”

Sources: Coding guidelines, Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 23b3d96b-34ec-4b4d-9bde-70aea6e6c1b3

📥 Commits

Reviewing files that changed from the base of the PR and between b7f2e17 and 01010e3.

📒 Files selected for processing (6)
  • src/main/git/git-capability-state.test.ts
  • src/main/git/git-capability-state.ts
  • src/main/git/git-execution-host.test.ts
  • src/main/git/git-execution-host.ts
  • src/main/git/runner-wsl-execution-mode.test.ts
  • src/main/git/runner.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 2 remain after this review.

Several places derived "which host runs this git call" independently, and two
disagreed on precedence. The runner routes by cwd -- a WSL UNC cwd names the
distro, and resolveCommand reads it before the caller's wslDistro hint.
git-capability-state preferred the hint.

So a call with a cwd in one distro and a hint naming another executed in the
cwd's distro while its capability results were filed under the hint's. A probe
answered by distro A then served calls claiming distro B, which is the failure
AGENTS.md's "scope capability state to the host that executes Git" warns about.

Resolve the host once, in git-execution-host.ts, and key routing, capability
state and output translation off it. Precedence follows execution: the cwd
wins, and the hint applies only when the cwd cannot name one. The one route
that overrides the name -- a prepared Windows-authored linked worktree running
host git.exe -- is passed in as `usesHostGit` so it resolves in the same place
instead of being compensated for by the caller.

Two consequences beyond the keying fix:

- The shell-free WSL read route no longer requires the caller to repeat what
  the cwd already says. It was gated on `options.wslDistro` being present, so
  a read with a WSL UNC cwd and no hint took `bash -c` -- and an explicit
  `preferWslDirectGit: true` was ignored outright on that path.
- The capability key folds distro case. Windows treats `\\wsl$\ubuntu` and
  `\\wsl.localhost\Ubuntu` as one host, so two spellings of one repo's distro
  were each re-probing every capability and eating its fallback.

Writes still pick their shell from the presence of `wslDistro` rather than from
the host and command class. That is characterized by a test here, not fixed:
flipping it decides whether every WSL write gets the user's PATH and ssh-agent,
which needs its own PR and real-Windows evidence.
@nwparker
nwparker force-pushed the OrcaWin/git-execution-owner branch from 01010e3 to ac3e2ba Compare August 18, 2026 21:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/git/runner.ts (1)

1222-1228: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the last opening fence for buffer output.

Login-shell output can echo the fenced command before the Git payload. Use Buffer.lastIndexOf so git show returns only the blob bytes. Add a regression test for an echoed opening fence.

Proposed fix
-  const beginIndex = stdout.indexOf(captured.beginMarker, 0, 'utf8')
+  const beginIndex = stdout.lastIndexOf(captured.beginMarker, undefined, 'utf8')

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ef2f0ca-fa38-4a4c-82a2-81c46608a46c

📥 Commits

Reviewing files that changed from the base of the PR and between 01010e3 and ac3e2ba.

📒 Files selected for processing (7)
  • src/main/git/git-capability-state.test.ts
  • src/main/git/git-capability-state.ts
  • src/main/git/git-execution-host.test.ts
  • src/main/git/git-execution-host.ts
  • src/main/git/runner-wsl-direct-read.test.ts
  • src/main/git/runner-wsl-execution-mode.test.ts
  • src/main/git/runner.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/git/git-capability-state.test.ts
  • src/main/git/runner-wsl-execution-mode.test.ts
  • src/main/git/git-capability-state.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant