Repository navigation
feat(cli): allow overriding the shell program with USAGE_SHELL_<SHELL> - #767
Conversation
|
Warning Review limit reached
Next review available in: 1 minute Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughShell execution now supports ChangesShell execution
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ShellExecution
participant Environment
participant ChildProcess
participant WSLHint
User->>ShellExecution: run script with requested shell
ShellExecution->>Environment: read USAGE_SHELL_* override
Environment-->>ShellExecution: selected executable or default
ShellExecution->>ChildProcess: spawn executable with script arguments
ChildProcess-->>ShellExecution: exit status
ShellExecution->>WSLHint: inspect Bash status and Windows path
WSLHint-->>User: print targeted guidance when matched
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cli/tests/shell_override.rs`:
- Around line 49-56: Update another_shell_can_stand_in so the shell selected by
USAGE_SHELL_BASH uses a POSIX-compatible fixture without set -o pipefail, or
replace /bin/sh with an interpreter that supports the existing usage_bash
fixture syntax. Preserve the test’s successful output comparison.
In `@docs/cli/scripts.md`:
- Around line 124-126: Correct the override example around USAGE_SHELL_BASH by
marking the fenced block as Command Prompt/batch syntax and using the properly
quoted set form; do not leave it labeled as Bash. If this section explicitly
targets multiple Windows terminals, also provide the corresponding PowerShell
assignment.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 1bee0899-a406-4c95-abec-29953af61185
📒 Files selected for processing (4)
cli/src/cli/shell.rscli/src/env.rscli/tests/shell_override.rsdocs/cli/scripts.md
dfb4e9e to
53b3f5f
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/cli/scripts.md`:
- Around line 116-119: Update the fenced code block containing the “usage bash
C:/work/mycli” example to include a language identifier such as text or console,
while preserving the example content.
- Around line 146-149: Update the documentation describing the `USAGE_SHELL_*`
value to state that an empty or whitespace-only value is treated the same as an
unset value, while preserving the existing explanation of inheritance and shell
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Central YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 18cf24b8-9996-4a03-8ef6-0fcdb98f0221
📒 Files selected for processing (4)
cli/src/cli/shell.rscli/src/env.rscli/tests/shell_override.rsdocs/cli/scripts.md
🚧 Files skipped from review as they are similar to previous changes (3)
- cli/tests/shell_override.rs
- cli/src/env.rs
- cli/src/cli/shell.rs
53b3f5f to
b5ebe20
Compare
Greptile SummaryThe PR adds per-shell environment-variable overrides for selecting the executable used by shell subcommands.
Confidence Score: 5/5The PR appears safe to merge, with only a non-blocking diagnostic-guidance issue remaining. The shell override behavior has no identified blocking failure; the remaining prior finding affects the remediation wording shown for drive-relative Windows paths. Files Needing Attention: cli/src/cli/shell.rs Important Files Changed
Reviews (3): Last reviewed commit: "feat(cli): allow overriding the shell pr..." | Re-trigger Greptile |
b5ebe20 to
4132764
Compare
On Windows, `usage bash C:/work/mycli` fails with
/bin/bash: C:/work/mycli: No such file or directory
Two facts combine. The Win32 executable search order puts the system
directory ahead of PATH, and installing WSL puts `bash.exe` there — so
`Command::new("bash")` picks the WSL launcher on such a machine regardless of
what else is installed. And the WSL bash cannot open a Windows path.
Translating the path is not a fix: the target spelling depends on which shell
was resolved (`/c/...` for msys, `/cygdrive/c/...` for cygwin, `/mnt/c/...`
for WSL, and WSL's mount point is itself configurable via /etc/wsl.conf).
usage has no way to know what `Command::new` resolved to short of
reimplementing the CreateProcess search order, which would be wrong in a
harder-to-diagnose way the moment it diverged from the real one.
So instead: name the shell. `USAGE_SHELL_BASH` (and _ZSH, _FISH, _PWSH)
replaces the program `usage <shell>` runs. The variable is keyed by the
program rather than the subcommand — `usage powershell` runs `pwsh`, so its
variable is USAGE_SHELL_PWSH, which also lets it point at powershell.exe on a
machine without pwsh. mise settled on the same shape for the same reason,
letting its shell settings hold an absolute path.
The value is a program, not a command line: shells on Windows live at paths
like `C:\Program Files\Git\bin\bash.exe`, and `Command` passes the program
and each argument separately, so a path with spaces needs no quoting and
usage takes on no quoting rules of its own.
Unset, blank, or a value that is only whitespace all mean "run the shell as
before", so nothing changes for anyone not setting it.
Two smaller improvements ride along. A shell that cannot be started now
reports which program was tried, and whether a variable pointed there — the
bare io error said only "No such file or directory". And on Windows, when
`bash` exits 127 having been handed a Windows path, usage explains what
probably happened and how to fix it; the guess is hedged and does not change
the exit code, because a script really can exit 127 on its own.
4132764 to
40c453e
Compare
⚠️ **CAUTION: this is a major update, indicating a breaking change!**⚠️ This MR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [usage](https://github.com/jdx/usage) | tools | major | `3.5.6` → `5.1.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/usage (usage)</summary> ### [`v5.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#510---2026-08-09) [Compare Source](jdx/usage@v5.0.0...v5.1.0) ##### 🚀 Features - **(spec)** parse usage comments from strings by [@​jdx](https://github.com/jdx) in [#​782](jdx/usage#782) ##### 🐛 Bug Fixes - **(spec)** avoid inferred metadata from included specs by [@​jdx](https://github.com/jdx) in [#​786](jdx/usage#786) ##### 🧪 Testing - **(windows)** make the suite runnable on Windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​771](jdx/usage#771) ##### 📦️ Dependency Updates - update rust crate rmcp to v3 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​780](jdx/usage#780) ### [`v5.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#500---2026-08-02) [Compare Source](jdx/usage@v4.1.0...v5.0.0) ##### 🚀 Features - **(cli)** allow overriding the shell program with USAGE\_SHELL\_<SHELL> by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​767](jdx/usage#767) ##### 🐛 Bug Fixes - **(cli)** forward parsed args to WSL bash via WSLENV on windows by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​764](jdx/usage#764) - **(cli)** let generate markdown write to stdout by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​766](jdx/usage#766) - **(complete)** use `type -P` so the CLI-presence guard ignores shell functions by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​760](jdx/usage#760) - **(parse)** enforce double\_dash="required" for positional args by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​762](jdx/usage#762) - **(windows)** run `run=` scripts with sh when available by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​765](jdx/usage#765) ##### 🎨 Styling - fix clippy and deprecation warnings in test and bench targets by [@​JamBalaya56562](https://github.com/JamBalaya56562) in [#​763](jdx/usage#763) ### [`v4.1.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#410---2026-07-30) [Compare Source](jdx/usage@v4.0.0...v4.1.0) ##### 🚀 Features - **(cli)** declare what each usage command does to the world by [@​jdx](https://github.com/jdx) in [#​751](jdx/usage#751) - **(mcp)** serve a usage spec to an agent over stdio by [@​jdx](https://github.com/jdx) in [#​746](jdx/usage#746) - **(spec)** add a top-level `repository` field by [@​jdx](https://github.com/jdx) in [#​747](jdx/usage#747) ##### 🐛 Bug Fixes - **(parse)** keep a re-declared global's aliases on one flag by [@​jdx](https://github.com/jdx) in [#​752](jdx/usage#752) - complete repeated variadic args by [@​Jai-JAP](https://github.com/Jai-JAP) in [#​753](jdx/usage#753) ##### New Contributors - [@​Jai-JAP](https://github.com/Jai-JAP) made their first contribution in [#​753](jdx/usage#753) ### [`v4.0.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#400---2026-07-25) [Compare Source](jdx/usage@v3.6.0...v4.0.0) ##### 🚀 Features - **(spec)** allow effect= on flags and args by [@​jdx](https://github.com/jdx) in [#​742](jdx/usage#742) ### [`v3.6.0`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#360---2026-07-25) [Compare Source](jdx/usage@v3.5.7...v3.6.0) ##### 🚀 Features - **(spec)** add effect= to declare what a command does to the world by [@​jdx](https://github.com/jdx) in [#​739](jdx/usage#739) ##### 🚜 Refactor - **(spec)** make missed SpecCommand fields a compile error, and fix the four that were already missed by [@​jdx](https://github.com/jdx) in [#​740](jdx/usage#740) ### [`v3.5.7`](https://github.com/jdx/usage/blob/HEAD/CHANGELOG.md#357---2026-07-25) [Compare Source](jdx/usage@v3.5.6...v3.5.7) ##### 🐛 Bug Fixes - **(parse)** don't leak the mounting CLI's flags into mounted commands; scan past non-global flags by [@​jdx](https://github.com/jdx) in [#​738](jdx/usage#738) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yODguMCIsInVwZGF0ZWRJblZlciI6IjQzLjI4OC4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWFqb3IiXX0=-->
usage 5.0.0 Created-by: HarmonybrewBot Commit-by: HarmonybrewBot Merged-by: HarmonybrewBot Description: Created by `brew bump` --- Created with `brew bump-formula-pr`.<details> <summary>release notes</summary> <pre>A parser-level fix that makes `double_dash="required"` actually behave as declared drives the major bump: values before `--` are now rejected, and values after `--` are routed past greedy variadics to the arg that was waiting for them. The release also fixes a cluster of long-standing Windows problems — `usage bash` losing every `usage_*` variable under WSL, `run=` scripts being handed to `cmd /c`, and completion guards being fooled by a shell function named `usage` — and lets `generate markdown` write to stdout like the other generators. ## Added - **Override the shell binary with `USAGE_SHELL_<SHELL>`** ([#767](jdx/usage#767) by @JamBalaya56562). Point `usage bash`, `usage zsh`, `usage fish`, and `usage powershell` at a specific interpreter — mainly so Windows users can escape the WSL `bash.exe` that Win32's search order picks up ahead of `$PATH`: ``` set USAGE_SHELL_BASH=C:\Program Files\Git\usr\bin\bash.exe usage bash C:/work/mycli ``` The variable is keyed by the program (so `powershell`'s override is `USAGE_SHELL_PWSH`). Unset, empty, or whitespace-only falls back to the default. Spawn failures now name the program that was tried and the variable it came from, and on Windows a `bash` exit 127 against a drive-letter path prints a hint pointing at this override. - **`generate markdown` writes to stdout** ([#766](jdx/usage#766) by @JamBalaya56562). `--out-file` is now optional and defaults to stdout, matching `manpage`, `fig`, `json`, and `completion`. `--out-file -` also means stdout on `markdown`, `manpage`, and `fig`, mirroring the `-f -` input convention. The `writing to …` progress line moved to stderr on `markdown`, `manpage`, `fig`, and `sdk`, so it no longer ends up inside the generated document. `--out-dir` now requires `--multi`. ## Fixed - **`double_dash="required"` is now enforced on both sides** ([#762](jdx/usage#762) by @JamBalaya56562). The parser previously ignored `SpecDoubleDashChoices::Required` entirely — a word offered to such an arg without `--` was accepted anyway, and a required arg sitting behind a greedy variadic was unreachable even with a separator. Now offering a value before `--` is reported as `ArgRequiresDoubleDash` (once per variadic, not once per word), and an explicit `--` routes the positional cursor onto the arg that required it, past earlier args. Completion learns about `--` too: while an arg is locked behind a separator, `--` itself is offered rather than values the parser would reject. - **Windows: `usage_*` variables reach WSL bash** ([#764](jdx/usage#764) by @JamBalaya56562). On Windows the `bash` picked up from the system directory is WSL's launcher, and WSL only forwards a Win32 variable when `WSLENV` names it — so scripts saw every `usage_*` value unset. Both `shell` and `exec` now append the parsed argument names to `WSLENV` (bare, no `/p` or `/l` flags), preserving any entries the user had already configured. - **Windows: `run=` scripts use `sh` when available** ([#765](jdx/usage#765) by @JamBalaya56562). `complete run=` already used `sh -c` everywhere, but `mount run=` used `cmd /c` on Windows, so the same POSIX one-liner behaved differently depending on which KDL node it lived in — and shebang scripts silently exited 0 with empty output. Both call sites now share one implementation: `sh -c` first, falling back to `cmd /c` only if `sh` is not found. Non-UTF-8 output from either shell is now reported as an error instead of panicking. - **Bash/fish completion guard ignores shell functions** ([#760](jdx/usage#760) by @JamBalaya56562). The generated completion opens with a guard that bails out when the `usage` CLI is not installed, but `type -p` returns exit 0 for a shell function, so any environment defining a `usage` function (e.g. oh-my-bash) passed the guard and then failed further down with an unrelated error. Switched to `type -P` in both bash guards and the fish equivalent; zsh's `type -p` already forces a `$PATH` search and is unchanged. ## Breaking Changes - **`double_dash="required"` positional args now reject values before `--`** ([#762](jdx/usage#762)). Specs where such an arg previously happened to work without a separator will now error. In `examples/mise.usage.kdl`, post-`--` values also move from the preceding greedy variadic to the arg that declared the separator (e.g. from `TASK_ARGS` to `TASK_ARGS_LAST`, from `TOOL@VERSION` to `COMMAND` under `exec`), which changes which `usage_*` variable a consumer reads. Spec authors who want the old permissiveness can drop back to `double_dash="optional"` (the default). - **`UsageErr` and `ParseOutput` gained fields.** `UsageErr` has a new `ArgRequiresDoubleDash` variant, and `ParseOutput` gained `next_arg` and `double_dash_seen`. Library consumers matching these types exhaustively will need to update. **Full Changelog**: jdx/usage@v4.1.0...v5.0.0 ## 💚 Sponsor usage usage is maintained by [@jdx](https://github.com/jdx), an open source developer for [**entire.io**](https://entire.io), the title sponsor of the [jdx.dev](https://jdx.dev) open source tools including [mise](https://mise.jdx.dev/), [aube](https://aube.jdx.dev/), hk, and more. Work on usage is funded by sponsorships. If `usage` powers CLI specs, docs, or completions for a tool you maintain or use, please consider [sponsoring at jdx.dev](https://jdx.dev/sponsors.html). Every sponsorship helps the project stay independent and moving. </pre> <p>View the full release notes at <a href="https://github.com/jdx/usage/releases/tag/v5.0.0">https://github.com/jdx/usage/releases/tag/v5.0.0</a>.</p> </details> <hr> See merge request: Harmonybrew/homebrew-core!15926
Problem
On Windows, handing
usage bashan absolute path fails:Two facts combine. The Win32 executable search order is application directory → current directory → system directory → … → PATH, with the system directory ahead of PATH. Installing WSL puts
bash.exethere, soCommand::new("bash")picks the WSL launcher on such a machine no matter what else is installed — confirmed on mine:OSTYPE=linux-gnu,uname -s = Linux, while PowerShell'sGet-Command bashreports Git Bash. And WSL's bash cannot open aC:\…path.Why not translate the path
The target spelling depends on which shell was resolved:
/c/…for msys/Git Bash,/cygdrive/c/…for Cygwin,/mnt/c/…for WSL — and WSL's mount point is itself configurable through/etc/wsl.conf. So even knowing it is WSL is not enough to translate correctly.More fundamentally, usage has no way to learn what
Command::new("bash")resolved to. Finding out would mean reimplementing the CreateProcess search order — App Paths,PATHEXT, safe-search mode — and that becomes a harder-to-diagnose bug the moment it diverges from what the OS actually does.Fix: name the shell
USAGE_SHELL_BASHreplaces the programusage bashruns.usage bashUSAGE_SHELL_BASHusage zshUSAGE_SHELL_ZSHusage fishUSAGE_SHELL_FISHusage powershellUSAGE_SHELL_PWSHThe variable is keyed by the program, not the subcommand.
usage powershellrunspwsh, so its variable is named for that — which also lets it point atpowershell.exeon a machine that only has Windows PowerShell. If this ever extends torun=execution,USAGE_SHELL_SHfalls out of the same rule.mise reached the same shape for the same reason: its
unix_default_*_shell_args/windows_default_*_shell_argsaccept an absolute path rather than trying to work out which shell you have.The value is a program, not a command line. Shells on Windows live at paths like
C:\Program Files\Git\bin\bash.exe, andCommandpasses the program and each argument separately, so a path with spaces needs no quoting — and usage takes on no quoting rules of its own. mise can afford the command-line form because its settings file also accepts arrays; a single environment variable cannot.Unset, empty, or whitespace-only all mean "run the shell as before", matching the
FOO= cmdconvention for switching something off. Nothing changes for anyone not setting it.Nothing checks that the program exists: the value need not be an absolute path, so validating would mean reimplementing
PATH,PATHEXTand permission lookup, then racing the spawn that follows. A bad value surfaces as a spawn error naming it.Two smaller improvements
A shell that cannot be started now says which program was tried, and whether a variable pointed there. The bare io error said only
No such file or directory (os error 2).And on Windows, when
bashexits 127 having been handed a Windows path, usage explains what probably happened:It fires only when all of: Windows, no override set, exit 127,
shell == "bash"(the only one of the four that ships in the system directory), and the script is a drive-letter or UNC path. It is hedged, prints after the child's own stderr, and does not touch the exit code — a script really can exit 127 on its own.Verified
Windows 11 with WSL installed. Before, with an absolute path: exit 127 plus the hint above. After pointing the variable at Git Bash, the same command works:
Five integration tests (
cli/tests/shell_override.rs,#[cfg(unix)]) cover the override actually replacing the program (USAGE_SHELL_BASH=/bin/echoprints the argv instead of running the script), a blank value falling back, a name resolved throughPATHworking like an absolute one, an unstartable program naming itself and the variable, and another shell's variable being ignored. Unit tests cover variable-name derivation, trimming, blank handling, and the hint's conditions and wording — all pure, so they run on the Linux CI.Those integration tests are Unix-only and I develop on Windows, so they are also run on Linux before pushing, not just type-checked and linted for
x86_64-unknown-linux-gnu.cargo test -p usage-lib --all-features,cargo clippy --all --all-features -- -D warningsandcargo fmt --all -- --checkpass. Help text is untouched, so no generated artifact changes.Deliberately not doing
bashourselves ahead of the system directory. It would change the resolution order for every user, away from what the OS does, and needs a new dependency. Worth discussing separately.usage exec. Its interpreter is already named by the caller, so a shebang can point straight at one:#!/usr/bin/env -S usage exec "C:/msys64/usr/bin/bash.exe". Generalising the variable to arbitrary program names would also mean a third party could redirect the interpreter a script author chose. Documented as the escape hatch instead.test.ymlisubuntu-latestonly, so the Windows path here rests on manual verification. That belongs to a separate CI change.This pull request was generated by Claude Code.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation