Repository navigation
Conversation
OrcaWin
force-pushed
the
OrcaWin/wsl-clean-machine-read-output
branch
3 times, most recently
from
August 17, 2026 07:41
9c4b437 to
18b0115
Compare
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.
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.
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.
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.
- 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.
OrcaWin
force-pushed
the
OrcaWin/wsl-clean-machine-read-output
branch
from
August 17, 2026 08:11
0e82843 to
fd6b9bf
Compare
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.
This was referenced Aug 18, 2026
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack
Merge bottom-up; each PR is based on the one above it.
main#15039#15040#15060ELI5
To find the user's tools, Orca runs WSL commands through the interactive login shell — the same one you get in a terminal — because that's the only shell that reads
~/.bashrc, where nvm/mise/asdf put things onPATH.The catch: an interactive shell also prints the welcome banner. On a stock Ubuntu 24.04, with nothing customized, that's:
That text goes to stdout — the same pipe Orca reads answers from. So when Orca asked "is this a file or a directory?", it got back the banner and then the answer, and the code compared the whole blob to
"directory". It never matched.This PR puts a marker around the real answer so Orca can find it, while keeping the PATH benefit.
Root cause
Reproduced on a stock distro, no user config:
buildWslLoginShellCommandselects-ilcfor bash/zsh (correctly —-lcalone misses~/.bashrc). Running the actual shipped code paths against a real distro:local-worktree-filesystem.statPath"To run a command as administrator…\n\ndirectory"local-worktree-filesystem.readPathrunPreflightCommandInWslgh --version/ auth outputcodex-trust-grant-hostbinary stamppath= the banner text (see below)statPathdoesstdout.trim(), which cannot recover this, so the type never equalsfile/directory. These two back the worktree-removal orphan-gitdir proof (getLocalWorktreePathAccess), so the proof reads a banner instead of a.gitfile — they are not the file-explorer listing path.That three call sites had each grown a different private workaround is the tell that this belongs in one place:
preflight-wsl-agent-detection.ts—__ORCA_AGENT_PATH__line prefixwsl-git-read-environment.ts—ORCA_WSL_GIT_READ_ENV_V1+lastIndexOfrate-limits/codex-fetcher.ts— an fd dance commented "before shell startup can … print banners"The ones that lacked a workaround are exactly the broken ones.
The silent one
codex-trust-grant-host.buildWslCodexBinaryStampreads the identity probe positionally — path before the first newline, version after. With the banner ahead of it the first newline lands inside the banner, so the stamp becomespath = "To run a command as administrator…"with the rest as version. Both halves are non-empty, so nothing throws. The stamp exists to detect "the Codex binary changed", so a garbage stamp reads as a changed binary and reissues the trust grant. This is the only case where the corruption is silent rather than a loud failure.What changed
buildWslCapturedLoginShellCommand(command)returns the script plus a matchingreadStdout. The payload is fenced:<BEGIN>and is dropped;~/.bash_logoutnoise lands after<END>.statPath'sexit 2→ENOENTmapping still works.catof any file that happens to quote it — an early build did exactly that, which is why the nonce exists.execs (the Codex identity probe) never writes a closing fence; the reader returns everything after the opening one.wsl-git-read-environmentdrops its bespoke marker and parsing.buildWslCodexIdentityArgsbecomesbuildWslCodexIdentityProbeand returns the reader alongside the argv so the two cannot drift apart.Deliberately not fenced: paths that
execinto a long-running program (codex app-server,codex login, terminal shells) — stdout belongs to that program there; and availability checks that are exit-code only. The git runner's login-shell mode also feeds streaming consumers, and git already has a shell-free--exec /usr/bin/env … gitfast path that is immune by construction.Testing
New real-WSL E2E (
local-worktree-filesystem-wsl-banner.wsl.test.ts, gated behindORCA_REAL_WSL_BANNER_TEST=1, following the existing.wsl.test.tsconvention). Against a liveUbuntu-24.04:It deliberately does not modify the developer's
~/.bashrc; it compares the fenced read against a raw login-shell read, so it is meaningful on both chatty and silent distros.The trust-grant fix is mutation-checked the same way — reverting it reproduces
path: "To run a command as administrator…".Unit tests cover banner stripping, exit-status preservation, payload-contains-a-fence, missing fence, early exit, and fence uniqueness.
This PR also fixes two tests that are currently red on
main.wsl-login-shell-command.test.tsandposix-command-path-lookup.test.tsboth assert a raw interactive login-shell read matches an expected value; on any machine with a stock Ubuntu they get the banner instead. The second sits in theshell contractsCI gate but skips on Linux, which is why the breakage stayed invisible.Known remaining (follow-up, not this PR)
Five call sites hand-write
bash -lc/sh -lcdirectly, bypassing both the shell detection and the fence — chiefly the Claude/Codex managed-auth path probes and the WSL CLI installer, which parse a path out of stdout and feed it onward as trusted.I measured this tier rather than assuming: on stock Ubuntu-24.04
-lcis clean — the banner is-i-gated. But that cleanliness is coincidental (01-locale-fix.shhappens to sort beforeZ99-cloud-locale-test.sh, which has no interactive guard and writes to stdout), and login shells read~/.bash_profile/~/.profileunconditionally — so an nvm/conda/direnv installer line or a corporate golden-image banner contaminates-lcexactly as much as-ilc:That is a real latent risk on ordinary developer machines, but it is a different tier from what this PR proves, and those sites share a common helper (
buildEncodedWslBashCommand) that makes them a clean separate change. Left as a follow-up rather than widening this diff.