mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
* 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>
73 lines
3.3 KiB
Markdown
73 lines
3.3 KiB
Markdown
# Running commands inside WSL
|
|
|
|
Two properties of `wsl.exe` decide how every guest invocation has to be written. Both are silent
|
|
when you get them wrong: the command still runs and still exits 0, it just returns the wrong bytes.
|
|
|
|
## 1. Always `--exec`, never `--`
|
|
|
|
`wsl.exe -d <distro> -- <argv>` expands `$name` in **every argument** against the guest environment
|
|
before the guest runs. This is `wsl.exe` itself, not the guest shell — it happens with no shell in
|
|
the command at all:
|
|
|
|
```
|
|
$ wsl.exe -d Ubuntu-24.04 -- /usr/bin/printf %s '$HOME'
|
|
/home/you
|
|
$ wsl.exe -d Ubuntu-24.04 --exec /usr/bin/printf %s '$HOME'
|
|
$HOME
|
|
```
|
|
|
|
So under `--`, a script means something other than what it says. `awk '{print $2}'` reaches the
|
|
guest as `awk '{print }'` and prints the whole line; a positional `"$1"`, a shell local, and a
|
|
`"\$literal"` are blanked or rewritten the same way. (Expansions with no `$` are unaffected — a
|
|
`sed` backreference like `s/(a)(b)/\2\1/` survives either way.) Escaping `$` on the Windows side
|
|
cannot fix this reliably — an earlier attempt skipped every `$` preceded by a backslash, which is
|
|
exactly the case a POSIX script uses to mean a literal dollar.
|
|
|
|
Build argv with `buildWslExecArgs()` in `src/shared/wsl-login-shell-command.ts`. A test walks the
|
|
tree and fails if the `--` form reappears.
|
|
|
|
The `--` inside `sh -s -- <path>` is a *shell* argument separator and is unrelated; leave it alone.
|
|
|
|
## 2. Machine-read output must be fenced
|
|
|
|
Orca runs guest commands through the distro user's **interactive** login shell (`-ilc` for
|
|
bash/zsh) because that is the only shell that reads `~/.bashrc`, where `nvm`, `mise` and `asdf`
|
|
install their PATH entries. Dropping `-i` would break tool detection for those users.
|
|
|
|
The cost is that an interactive shell also runs the distro's rc/motd, and that output goes to
|
|
**stdout** — the same stream the answer arrives on. Stock Ubuntu 24.04 needs no customization to
|
|
reproduce it:
|
|
|
|
```
|
|
$ wsl.exe -d Ubuntu-24.04 --exec bash -ilc 'git --version'
|
|
To run a command as administrator (user "root"), use "sudo <command>".
|
|
See "man sudo_root" for details.
|
|
|
|
git version 2.43.0
|
|
```
|
|
|
|
Any caller that parses stdout must use `buildWslCapturedLoginShellCommand()`, which fences the
|
|
payload and returns a matching `readStdout`. `.trim()` does not help: the banner is a prefix, not
|
|
surrounding whitespace, so a stat probe compared against `"directory"` simply never matches.
|
|
|
|
The fence carries a per-call nonce so that `cat`-ing a file whose contents happen to quote a marker
|
|
is not truncated, and it preserves the payload's exit status so `exit 2` → `ENOENT` mappings keep
|
|
working.
|
|
|
|
**Do not fence a command that `exec`s into a long-running program** (`codex app-server`, an
|
|
interactive terminal). It never reaches the closing fence, and there the shell's own output either
|
|
belongs to the program or is what the user wants to see.
|
|
|
|
## Prefer no shell at all
|
|
|
|
When a caller only needs a known binary with a known environment, skip the login shell entirely and
|
|
run the binary directly:
|
|
|
|
```
|
|
wsl.exe -d <distro> --exec /usr/bin/env PATH=… HOME=… /usr/bin/git -C <dir> status
|
|
```
|
|
|
|
This is what the direct-git read path does. It is immune to both problems above by construction and
|
|
avoids paying login-shell startup on every call, which also sidesteps profiles that block or print.
|
|
Resolve the PATH/HOME once through a fenced probe, cache it per distro, then use this form.
|