Skip to content

fix(git): isolate WSL login-shell startup from Git stdio - #12022

Closed
rodboev wants to merge 4 commits into
stablyai:mainfrom
rodboev:pr/orca-10917-wsl-git-output-framing
Closed

rodboev wants to merge 4 commits into
stablyai:mainfrom
rodboev:pr/orca-10917-wsl-git-output-framing

Conversation

@rodboev

@rodboev rodboev commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Isolate WSL login-shell startup from Git stdin, stdout, and stderr so shell banners cannot corrupt GitHub source detection.
  • Preserve interactive-login PATH initialization, the existing Git command string and argv, binary and streamed output, status and launch errors, and routes outside local WSL Git.

The shared WSL login-shell builder has an opt-in descriptor phase. resolveCommand selects it only for local WSL Git; default helper callers retain their generated command.

Base/head proof used origin/main 8fc892dd023850df379c07cc4e7a46cff9a3ea84: the extracted base failed the public issue banner identity assertion with null; the rebuilt head passed with { owner: 'stablyai', repo: 'orca' }.

Closes #10917.

Screenshots

No visual change

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test
  • pnpm build
  • Added focused regression tests for startup banners, exact WSL argv, stdin, binary and streamed channels, status, launch errors, and negative-space routes.

Focused validation:

  • Targeted Vitest: 3 files, 45 tests passed, 1 skipped.
  • Post-fix WSL shell regression checks: 17 tests passed, 1 skipped.
  • Related preflight/rate-limit Vitest: 4 files, 87 tests passed.
  • GitHub identity Vitest: 2 files, 60 tests passed.
  • pnpm run typecheck:node, changed-file oxlint, changed-file oxfmt --check, pnpm run check:max-lines-ratchet, and git diff --check passed.
  • Full suite and build were not run; CI owns those checks.

AI Review Report

The final diff was reviewed for Windows-to-WSL argv construction, login-shell branches, caller stdin, text and binary channels, Git status, shell-owned setup behavior, synchronous and event-based launch errors, and native, generic WSL, non-Git login-shell, SSH-provider, and default-helper negative space. Native Git remains direct binary-plus-argv execution on macOS and Linux. No UI or rendering path changed.

Security Audit

Descriptor commands are fixed shell text. Existing POSIX quoting and Windows-to-WSL dollar escaping remain in place, and descriptors 3, 4, and 5 close before the Git payload. No marker parsing, output file, process, dependency, credential, network, IPC, persistence, or user-facing copy surface was added.

Notes

  • Scope is the shared WSL login-shell startup boundary and the local WSL Git opt-in.
  • Native Git, generic WSL, non-Git login-shell, gh, glab, SSH-provider execution, and default shared-helper output remain unchanged.
  • Explicit Git path lookup, synthesized missing-Git diagnostics, output markers, temporary files, and UI behavior are out of scope.
  • Current WSL state is stopped docker-desktop, WSL2. Real user-distro runtime proof was not run because no representative distro is available.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a45d6191-4b66-460f-91bb-57ff8bad50ba

📥 Commits

Reviewing files that changed from the base of the PR and between db9abdf and 70dd937.

📒 Files selected for processing (2)
  • src/main/git/runner-wsl-login-shell-stdio.test.ts
  • src/shared/wsl-login-shell-command.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/shared/wsl-login-shell-command.ts

📝 Walkthrough

Walkthrough

The WSL login-shell command builder now supports startup-stdio isolation and exposes the outer-shell arguments. Git WSL execution enables isolation for Git commands. Tests cover payload construction, descriptor handling, binary output, process and stream errors, exit statuses, missing resources, and native, generic WSL, and SSH routing.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Git WSL login-shell stdio isolation fix.
Description check ✅ Passed The description covers all required sections and documents testing, review scope, security risks, and platform notes.
Linked Issues check ✅ Passed The changes isolate startup output for local WSL Git while preserving login-shell behavior and unaffected execution routes required by issue #10917.
Out of Scope Changes check ✅ Passed The code and tests remain focused on WSL login-shell startup stdio isolation and local WSL Git routing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/shared/wsl-login-shell-command.ts (1)

26-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a one-line rationale for the descriptor parking.

The descriptor sequence is correct. The outer shell parks caller stdin, stdout, and stderr on 3, 4, and 5, points startup stdio at /dev/null, and the inner script restores them before the payload runs. That ordering is what suppresses inner .bashrc banners, because rc files execute before the script body.

The mechanism is non-obvious. A single // Why: line matches the existing comment style in this file and prevents a later refactor from moving the restore lines out of the inner script.

♻️ Proposed comment
+  // Why: rc banners run before the inner script body, so restoring fds inside the body drops startup output only (issue `#10917`).
   const quotedCommand = quotePosixShell(

Source: Coding guidelines

src/shared/wsl-login-shell-command.test.ts (1)

97-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Skip this test explicitly on Windows instead of returning on ENOENT.

On Windows there is no sh on PATH, so execFileSync throws ENOENT and line 123 returns. The test then reports as passed while it verified nothing. An explicit skip states the platform contract and keeps the pass signal honest.

The success path also throws inside try, and the same catch handles it. That produces the message "POSIX shell integration failed: expected the payload to exit with status 7", which is misleading. Move the "expected status 7" failure out of the try block or assert on a captured result.

♻️ Proposed change
-  it('isolates startup output and restores caller stdin in a headless POSIX shell', () => {
+  it.skipIf(process.platform === 'win32')(
+    'isolates startup output and restores caller stdin in a headless POSIX shell',
+    () => {

Based on learnings: tests that depend on platform-specific capabilities should be guarded with it.skipIf(process.platform === 'win32') so the assertions are not silently skipped.

Source: Learnings

src/main/git/runner.ts (1)

234-244: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the repeated command === 'git' check.

Line 223 already tests command === 'git' to select localePrefix. Line 234 repeats the same literal. A single const isGit = command === 'git' above line 223 keeps the two Git-specific behaviors tied to one predicate.

♻️ Proposed change
-    const isolateStartupStdio = command === 'git'
+    const isolateStartupStdio = isGit
src/main/git/runner-wsl-login-shell-stdio.test.ts (2)

157-225: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a fresh child per scenario in this test.

This test covers four scenarios and reuses the single child emitter created at line 159. The listeners registered at lines 180 through 189 stay attached when line 208 and line 217 emit 'error' on the same emitter. The scenarios pass today, but a later change to listener ordering can produce cross-talk that is hard to diagnose.

Create a new fakeChild() inside each spawnMock/execFileMock implementation, or split the four scenarios into separate it blocks. Separate blocks also give precise failure names for spawned channels, launch errors, execFile child errors, and spawn child errors.


285-287: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace as never with an explicit intent cast.

as never disables all type checking on the options object and hides which required field is omitted. The test omits cwd on purpose. State that with a cast through unknown, or build the object with an explicit partial type.

♻️ Proposed change
-      await expect(gitExecFileAsync(['status'], { wslDistro: 'Ubuntu' } as never)).rejects.toBe(
-        missingGit
-      )
+      await expect(
+        gitExecFileAsync(['status'], { wslDistro: 'Ubuntu' } as unknown as GitExecOptions)
+      ).rejects.toBe(missingGit)

Import GitExecOptions as a type from ./runner if it is exported.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: abc17466-162e-4aed-b97a-c036dcde686d

📥 Commits

Reviewing files that changed from the base of the PR and between 8fc892d and a6758d7.

📒 Files selected for processing (5)
  • src/main/git/runner-command-exec.test.ts
  • src/main/git/runner-wsl-login-shell-stdio.test.ts
  • src/main/git/runner.ts
  • src/shared/wsl-login-shell-command.test.ts
  • src/shared/wsl-login-shell-command.ts

Comment thread src/shared/wsl-login-shell-command.ts Resolved
@greptile-apps

greptile-apps Bot commented Aug 2, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR fixes issue #10917 by isolating the WSL login-shell startup phase from Git's stdio channels. When a Git command is routed through WSL, a new isolateStartupStdio mode parks the caller's FD 0/1/2 onto FDs 3/4/5, redirects startup stdio to /dev/null (so login-script banners can't corrupt Git's output), then restores the original descriptors inside the inner-shell payload immediately before Git runs.

  • buildWslLoginShellCommand gains an { isolateStartupStdio } option that prepends FD-parking lines and embeds FD-restoration lines in the inner-shell payload; getWslLoginShellOuterArgs() is extracted as a new export.
  • resolveCommand in runner.ts opts into isolateStartupStdio only when the command is git, leaving all other WSL callers unchanged.
  • A new test file covers banner rejection, exact argv, binary output, spawn channels, streaming, and negative-space routing; one assertion in the existing runner-command-exec.test.ts was changed to expect -c instead of -lc, contradicting the implementation.

Confidence Score: 4/5

Safe to merge after fixing the one broken test assertion; the core stdio-isolation logic is correct and well-tested by the new file.

The FD-parking/restoration logic in wsl-login-shell-command.ts, the isolateStartupStdio opt-in in runner.ts, and the full new test suite are all internally consistent and correct. One assertion in runner-command-exec.test.ts was changed to expect -c but the implementation produces -lc, so that test will fail when the file is executed. Everything else in the changeset is clean.

Files Needing Attention: src/main/git/runner-command-exec.test.ts — the outer-flag assertion at line 490 needs to be reverted to -lc.

Important Files Changed

Filename Overview
src/shared/wsl-login-shell-command.ts Adds getWslLoginShellOuterArgs() and optional isolateStartupStdio mode; parks caller FDs 3/4/5, redirects startup stdio to /dev/null, and restores them in the inner-shell payload.
src/main/git/runner.ts Wires isolateStartupStdio = command === 'git' and getWslLoginShellOuterArgs() into resolveCommand; non-git WSL login-shell callers are unchanged.
src/main/git/runner-wsl-login-shell-stdio.test.ts New test file covering banner isolation, exact wsl.exe argv (expects outer -lc), binary output, spawn channels/errors, streaming, and negative-space routing.
src/main/git/runner-command-exec.test.ts One assertion changed from -lc to -c contradicts the implementation and the new test file which both expect -lc; another added assertion correctly checks -lc and no-isolation.
src/shared/wsl-login-shell-command.test.ts Adds unit tests for isolateStartupStdio: FD ordering, null-isolation, and a POSIX integration test confirming startup output is suppressed and stdin is restored.

Sequence Diagram

sequenceDiagram
    participant Node as Node.js gitExecFileAsync
    participant wsl as wsl.exe sh -lc
    participant outer as Outer sh startup
    participant inner as Inner bash -ilc payload
    participant git as git

    Node->>wsl: spawn with stdin/stdout/stderr pipes
    wsl->>outer: "park FDs 3=stdin 4=stdout 5=stderr"
    Note over outer: redirect all stdio to devnull
    Note over outer: login scripts and banners go to devnull
    outer->>outer: detect shell via getent passwd
    outer->>inner: exec bash -ilc with restore commands
    Note over inner: restore original pipes from FD 3/4/5
    inner->>git: run with restored stdio
    git-->>Node: clean stdout with no banner prefix
Loading

Reviews (3): Last reviewed commit: "Prevent banner output from corrupting WS..." | Re-trigger Greptile

Comment thread src/shared/wsl-login-shell-command.ts Resolved
@rodboev

rodboev commented Aug 21, 2026

Copy link
Copy Markdown
Contributor Author

Closing this as superseded by PR #15257, which merged the same WSL Git read fix into main at commit 6efd406.

@rodboev rodboev closed this Aug 21, 2026
@nwparker

Copy link
Copy Markdown
Contributor

Thanks @rodboev for all the changes generally...

Actually trying to fix all the WSL this week.
LMK if you want to send me a list of the issues that bother you or we can sync on Discord (ping me there!)
Let's knock these out 🫡

@rodboev

rodboev commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, just getting back to these. I use a patched build so I'll have to see what's still live on current main and I'll ping you when I have the list.

@nwparker

Copy link
Copy Markdown
Contributor

Thanks, just getting back to these. I use a patched build so I'll have to see what's still live on current main and I'll ping you when I have the list.

Thanks!
feel free to ping me on X or Discord too if I miss any message!

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.

[Bug]: git output is parsed from an interactive login shell (-ilc), so any shell-rc banner (fastfetch/neofetch/motd) breaks GitHub source detection

4 participants