Skip to content

fix(cli): forward parsed args to WSL bash via WSLENV on windows - #764

Draft
JamBalaya56562 wants to merge 1 commit into
jdx:mainfrom
JamBalaya56562:fix-wslenv-windows
Draft

fix(cli): forward parsed args to WSL bash via WSLENV on windows#764
JamBalaya56562 wants to merge 1 commit into
jdx:mainfrom
JamBalaya56562:fix-wslenv-windows

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

On Windows with WSL installed, a script run through usage bash sees every usage_* variable unset — no error, no warning, just empty values.

$ usage bash ./envtest.sh myws --region eu-1
workspace: []
region: []

Cause

Two independent facts combine.

1. The bash that gets resolved is always WSL's. The Win32 executable search order is application directory → current directory → system directory → … → PATH, with the system directory ahead of PATH. Installing WSL puts C:\Windows\System32\bash.exe there, so Command::new("bash") in cli/src/cli/shell.rs picks the WSL launcher no matter what else is on PATH. A diagnostic script confirms it: OSTYPE=linux-gnu, uname -s = Linux. Note that PowerShell's Get-Command bash reports Git Bash on the same machine, which is why inspecting PATH does not reveal this.

2. WSL only carries a Win32 variable across the boundary if WSLENV names it. So the cmd.env(key, val) loop in shell.rs (and the identical one in exec.rs) transfers nothing.

Fix

Both call sites now go through a shared env::apply_parsed_env, which sets the variables as before and, on Windows, also appends their names to WSLENV.

Names are added bare, with no flags. WSLENV supports /p (translate as a path) and /l (as a path list), but usage has no idea whether a given value is a path — /p would silently rewrite anything that merely looks like one, and variadic values are joined with shell_words::join, so they are not a ;-separated path list either. Unflagged names copy the value verbatim, so a script receives the same bytes it would on Unix.

Entries already present in WSLENV are preserved exactly, flags included: a name the user configured is theirs, not ours to redefine.

cfg!(windows) rather than #[cfg(windows)], because test.yml runs on ubuntu-latest only (the Windows job in publish-cli.yml is a tag-triggered cargo build with no test or clippy). A #[cfg] block here would never be compiled, type-checked or linted anywhere.

Why not change which shell gets resolved

Deliberately out of scope. Changing the resolution would break scripts that already depend on WSL bash, and there is no single right answer — some Windows users want WSL, others want Git Bash or MSYS2. For comparison, mise does not guess either: it defaults to cmd /c on Windows and lets the shell be specified explicitly, including as an absolute path. usage-lib already follows the same philosophy in lib/src/sh.rs, which uses sh -c on Unix and cmd /c on Windows. Only the usage <shell> <script> path assumes bash, and that assumption comes from the script's own shebang.

This PR restores a transport that was silently dropping data; it does not decide policy.

Verified

Windows 11 + WSL2, before and after:

before:  --- usage_* in env ---
         (no usage_* vars in env)
         workspace=[<unset>]   region=[<unset>]

after:   --- usage_* in env ---
         usage_workspace=myws
         usage_region=eu-1
         workspace=[myws]      region=[eu-1]

With a pre-existing WSLENV=MY_EXISTING and MY_EXISTING=hello, that entry still transfers alongside the new ones (MY_EXISTING=[hello]).

11 unit tests cover the WSLENV string building — ordering, appending, dedup against existing bare and flagged names, empty-segment handling, and the no-flags guarantee. They are pure-function tests, so they run on the Linux CI. One further test pins the invariant they rely on: that as_env() never produces a key containing : or /, which would otherwise corrupt the list.

No integration test is added — it would need Windows plus WSL2, and there is no Windows runner in CI, so a #[cfg(windows)] #[ignore] test would simply rot.

Not covered

Path translation (a Windows path passed as an argument still arrives as C:\..., unusable inside WSL), shell resolution, and any behaviour off Windows (WSLENV is untouched there).


This pull request was generated by Claude Code.

Summary by CodeRabbit

  • Bug Fixes
    • Improved environment-variable handling when launching commands and interactive shells.
    • On Windows, environment variables are now reliably passed through to WSL.
    • Existing WSLENV settings and flags are preserved, while duplicate or invalid entries are safely excluded.
    • Environment propagation is now more consistent across supported command execution modes.

On Windows with WSL installed, `usage bash script.sh myws --region eu-1`
runs the script with every `usage_*` variable unset. No error, no warning —
the script just sees empty values.

Two independent facts combine:

- The Win32 executable search order puts the system directory ahead of PATH,
  so `Command::new("bash")` resolves to `C:\Windows\System32\bash.exe`, the
  WSL launcher, regardless of what else is on PATH. (`OSTYPE=linux-gnu`,
  `uname -s = Linux`, while PowerShell's `Get-Command bash` reports Git Bash —
  which is why looking at PATH does not reveal this.)
- WSL only carries a Win32 variable across the boundary if `WSLENV` names it.

So the `cmd.env(...)` calls in `shell.rs` and `exec.rs` are a no-op there.

Both call sites now go through `env::apply_parsed_env`, which additionally
adds the variable names to `WSLENV` on Windows. Names are added bare: usage
does not know whether a value is a path, and `/p` would silently rewrite
anything that merely looks like one, so values cross verbatim as they do on
Unix. Entries already in `WSLENV` are preserved untouched, flags included.

Which bash gets resolved is deliberately left alone. Changing it would break
scripts already relying on WSL bash, and there is no single right answer —
mise, for comparison, does not guess either: it defaults to `cmd /c` on
Windows and lets the shell be configured explicitly.

`cfg!(windows)` rather than `#[cfg(windows)]` because CI only runs on Linux,
where a `#[cfg]` block would never be compiled, type-checked or linted.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 20a41980-ce35-4b78-a7e3-d05b28867308

📥 Commits

Reviewing files that changed from the base of the PR and between 4b2fde7 and 7974065.

📒 Files selected for processing (3)
  • cli/src/cli/exec.rs
  • cli/src/cli/shell.rs
  • cli/src/env.rs

📝 Walkthrough

Walkthrough

The change centralizes parsed environment application for Exec and Shell. On Windows, it also updates WSLENV with validated, deduplicated variable names while preserving existing entries and flags.

Changes

Parsed environment propagation

Layer / File(s) Summary
Environment application and WSLENV handling
cli/src/env.rs
Adds apply_parsed_env and append_to_wslenv. The implementation applies variables, filters unsafe keys, preserves flags, removes empty segments, prevents duplicates, and includes unit tests.
Command launch integration
cli/src/cli/exec.rs, cli/src/cli/shell.rs
Exec::run and Shell::run delegate parsed environment handling to env::apply_parsed_env.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ExecOrShell
  participant apply_parsed_env
  participant Command
  participant WSLENV
  ExecOrShell->>apply_parsed_env: pass parsed environment
  apply_parsed_env->>Command: set environment variables
  apply_parsed_env->>WSLENV: append valid names on Windows
Loading

Poem

A rabbit taps WSLENV with care,
While parsed keys hop through the air.
Exec and Shell share the track,
Safe names go forth and flags come back.
Tests applaud: “No duplicates there!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes forwarding parsed arguments to WSL bash through WSLENV on Windows.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

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.

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