Skip to content

fix(task): resolve bash deterministically on Windows to avoid WSL launcher#9750

Merged
jdx merged 2 commits into
jdx:mainfrom
JamBalaya56562:fix/bug-e-bash-resolve-windows
May 9, 2026
Merged

fix(task): resolve bash deterministically on Windows to avoid WSL launcher#9750
jdx merged 2 commits into
jdx:mainfrom
JamBalaya56562:fix/bug-e-bash-resolve-windows

Conversation

@JamBalaya56562

Copy link
Copy Markdown
Contributor

Summary

When mise spawns bash -c for a task on Windows, prefer a real POSIX bash
(Git Bash / MSYS2) over the WSL launcher at C:\Windows\System32\bash.exe.
The WSL launcher is on PATH first when mise is invoked from PowerShell, and
routing into WSL means the spawned task body runs inside a separate Linux
user-space where mise-managed Windows tools aren't visible — tasks fail with
command not found even though mise install succeeded.

Reproduction

[tools]
bun = "latest"

[tasks.bun-via-bash]
shell = "bash -c"
run = "bun --version"

From Git Bash — works

$ MISE_VERBOSE=1 mise run bun-via-bash
[bun-via-bash] $ bun --version
DEBUG $ C:\Program Files\Git\usr\bin\bash.exe -c bun --version
1.3.13

From PowerShell — fails

PS> $env:MISE_VERBOSE='1'; mise run bun-via-bash
[bun-via-bash] $ bun --version
DEBUG $ C:\WINDOWS\system32\bash.exe -c bun --version
/bin/bash: line 1: bun: command not found
[bun-via-bash] ERROR task failed

uname -s inside the spawned bash returns Linux, confirming the body ran
inside WSL rather than on Windows. Same mise.toml works or fails based
purely on which terminal the user ran mise from.

Why

src/task/task_executor.rs::resolve_posix_shell_program_path calls
which::which_in(&task_env_PATH) to make bash an absolute path before
spawning. Under PowerShell, C:\Windows\System32 precedes any Git Bash
directory on PATH, so the WSL launcher wins. Under Git Bash, MSYSTEM
ordering puts /usr/bin first, so the real bash wins. The previous code had
no Git Bash preference and no WSL-launcher rejection.

Resolution order

The bash-specific path inside resolve_posix_shell_program_path:

  1. MISE_BASH_PATH env var — explicit user override (task env, then process env).
  2. Common Git Bash install locations:
    • C:\Program Files\Git\bin\bash.exe
    • C:\Program Files (x86)\Git\bin\bash.exe
    • %LOCALAPPDATA%\Programs\Git\bin\bash.exe
  3. The existing which::which_in(&task_env_PATH) search.
  4. Final guard: if PATH search returns the WSL launcher
    (...\Windows\System32\bash.exe or ...\Microsoft\WindowsApps\bash.exe),
    reject it with a warning and let the spawn fail loudly rather than silently
    routing into WSL.

Scope

  • Bash only. sh/zsh/fish/ksh/dash keep the existing behavior.
  • Windows only. The relevant code path is already #[cfg(windows)]; Linux
    and macOS builds are unaffected — both resolve_posix_shell_program_path
    and maybe_convert_env_for_msys_shell sit behind #[cfg(windows)] gates.

Test plan

  • cargo fmt --check clean

  • Unit tests added (Windows-gated):

    • is_bash_basename — accepts bash / bash.exe / BASH.EXE / absolute paths; rejects sh / zsh.exe / fish / dash / cmd.exe / bashfoo
    • is_wsl_launcher_bash — detects C:\Windows\System32\bash.exe (any drive, any case) and ...\Microsoft\WindowsApps\bash.exe (forward or back slash); accepts C:\Program Files\Git\bin\bash.exe, C:\Program Files\Git\usr\bin\bash.exe, C:\msys64\usr\bin\bash.exe, scoop / per-user installs
    • git_bash_candidates — emits Program Files paths; appends a LOCALAPPDATA\Programs\Git\bin\bash.exe candidate when LOCALAPPDATA is in the task env
    • resolve_posix_shell_program_pathMISE_BASH_PATH override is honored via tempdir; non-POSIX shells skip the path; Unix-form PATH skips the path
  • All 15 tests in task::task_executor::tests::* pass on Windows MSVC build

  • Manual verification on Windows 11 with locally built mise:

    Invoker Before fix After fix
    PowerShell resolves WSL launcher → command not found resolves Git Bash → task succeeds
    Git Bash resolves Git Bash → task succeeds resolves Git Bash → task succeeds (unchanged)

WSL Linux build of mise was not exercised because the relevant code is
#[cfg(windows)] and is not compiled on Linux targets.

Adjacent / related

🤖 Generated with Claude Code

…ncher

When mise spawns `bash -c` for a task on Windows, prefer a real POSIX bash
(Git Bash / MSYS2) over the WSL launcher at C:\Windows\System32\bash.exe.
The WSL launcher is on PATH first when mise is invoked from PowerShell, and
routing into WSL means the spawned task body runs inside a separate Linux
user-space where mise-managed Windows tools aren't visible — tasks fail with
'command not found'.

Resolution order in resolve_posix_shell_program_path() for bash specifically:

1. MISE_BASH_PATH env var (explicit override).
2. Common Git Bash install locations (Program Files, Program Files (x86),
   %LOCALAPPDATA%\Programs\Git\bin\bash.exe).
3. The existing which::which_in() PATH search.
4. Final guard: if PATH search returns the WSL launcher
   (...\Windows\System32\bash.exe or ...\Microsoft\WindowsApps\bash.exe),
   reject it with a warning and let the spawn fail loudly rather than
   silently dispatching into WSL.

Scope:
- Bash only. sh/zsh/fish/ksh/dash keep the existing behavior.
- Windows only. Linux/macOS builds are unaffected — the relevant code path
  was already #[cfg(windows)].

Tests:
- Pure helpers (is_bash_basename, git_bash_candidates, is_wsl_launcher_bash)
  each get unit tests covering case-folding, forward/back slashes, and
  realistic install paths.
- An integration test for resolve_posix_shell_program_path verifies that
  MISE_BASH_PATH overrides PATH search using a tempdir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a Windows-specific bug where mise tasks using shell = "bash -c" silently routed into WSL when invoked from PowerShell, because C:\Windows\System32\bash.exe (the WSL launcher) appeared first on PATH. The fix adds a deterministic resolution strategy for bash in resolve_posix_shell_program_path that consults MISE_BASH_PATH, probes common Git Bash / MSYS2 install locations, and then walks PATH via which_in_all filtering out any WSL launchers before falling back.

  • src/task/task_executor.rs: New helpers is_bash_basename, bash_candidates, and is_wsl_launcher_bash drive the bash-specific resolution; 15 new #[cfg(windows)] unit tests cover all branches including MSYS2 paths and case-insensitive drive letters.
  • src/path.rs: Refactors the basename-stem extraction into a reusable program_stem helper used by both is_posix_shell_program and the new is_bash_basename.
  • docs/troubleshooting.md: Adds a clear troubleshooting entry documenting the WSL-launcher symptom and the MISE_BASH_PATH override.

Confidence Score: 5/5

Safe to merge. All new logic is Windows-only and bash-specific; Linux/macOS builds are unaffected. The fallback (returning None and letting spawn fail loudly) is strictly better than the previous silent WSL routing.

The change is narrowly scoped behind #[cfg(windows)], the new helper functions each have their own unit tests, the refactored program_stem extracts existing logic without behaviour change, and the fallback path was already the safe pre-existing default for all other shells.

No files require special attention. src/task/task_executor.rs carries the most new logic but is thoroughly tested.

Important Files Changed

Filename Overview
src/task/task_executor.rs Adds bash-specific resolution logic inside resolve_posix_shell_program_path: checks MISE_BASH_PATH override, probes static Git Bash / MSYS2 candidate paths, then walks PATH via which_in_all filtering out the WSL launcher. New helper functions are well-scoped behind #[cfg(windows)] with thorough unit tests.
src/path.rs Extracts the basename-stem logic from is_posix_shell_program into a new public program_stem helper, then rewrites is_posix_shell_program to use it. Clean refactor; behaviour is identical.
docs/troubleshooting.md Adds a new Windows-specific troubleshooting section explaining the WSL-launcher pitfall and documenting the MISE_BASH_PATH override. Clear and accurate.

Reviews (2): Last reviewed commit: "fix(task): broaden Windows bash candidat..." | Re-trigger Greptile

Comment thread src/task/task_executor.rs Outdated
Comment thread src/task/task_executor.rs

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request improves bash resolution on Windows by prioritizing real POSIX bash installations over the WSL launcher, which can interfere with Windows-managed tools. It introduces the MISE_BASH_PATH environment variable, checks common Git Bash installation paths, and adds logic to ignore the WSL launcher. Review feedback suggests using which::all_in to find the first valid bash on the PATH and refactoring the program stem extraction logic into a shared helper to avoid duplication.

Comment thread src/task/task_executor.rs Outdated
Comment thread src/task/task_executor.rs
…cher across PATH

Addresses review feedback on PR jdx#9750.

- Add MSYS2 standalone install paths (`C:\msys64\usr\bin\bash.exe`,
  `C:\msys32\usr\bin\bash.exe`) to the candidate list so MSYS2-only
  users get the auto-fix without having to set MISE_BASH_PATH. The helper
  is renamed bash_candidates accordingly.
- For bash, walk every PATH match via which::which_in_all and pick the
  first entry that isn't the WSL launcher. This rescues setups where a
  real POSIX bash is on PATH but appears after C:\Windows\System32. The
  non-bash path keeps the original which::which_in (single-result) call
  to avoid changing behavior for sh/zsh/fish/ksh/dash.
- Mention MSYS2 in the warning message emitted when only the WSL launcher
  remains, so users in that situation get pointed at the right escape
  hatch.
- Extract crate::path::program_stem and reuse it from both
  is_posix_shell_program and is_bash_basename, removing the duplicated
  basename + .exe-strip parsing.

All 16 task::task_executor::tests::* and 12 path::tests::* unit tests
pass on Windows MSVC. Manual reverification on PowerShell and Git Bash
both resolve bash to C:\Program Files\Git\bin\bash.exe and the bun
task runs to completion (1.3.13, MINGW64_NT uname).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx merged commit 14f2830 into jdx:main May 9, 2026
34 checks passed
@JamBalaya56562 JamBalaya56562 deleted the fix/bug-e-bash-resolve-windows branch May 10, 2026 08:46
mise-en-dev added a commit that referenced this pull request May 10, 2026
### 🚀 Features

- add --inactive option to outdated and upgrade commands for inactive
tools by @roele in [#9640](#9640)

### 🐛 Bug Fixes

- **(aqua)** resolve bin paths for prefixed v tags by @risu729 in
[#9759](#9759)
- **(bun)** create bunx alongside bun.exe on Windows install by
@JamBalaya56562 in [#9732](#9732)
- **(dotnet)** use shared prerelease tool option by @risu729 in
[#9720](#9720)
- **(node)** use matching node in npm shim by @jdx in
[#9749](#9749)
- **(task)** resolve bash deterministically on Windows to avoid WSL
launcher by @JamBalaya56562 in
[#9750](#9750)

### 📚 Documentation

- **(secrets)** clarify age strict mode default by @risu729 in
[#9737](#9737)
- **(tasks)** add bash shebang to conditional-dependencies example by
@JamBalaya56562 in [#9747](#9747)
- update backend tool option docs by @risu729 in
[#9738](#9738)

### 📦 Registry

- remove tools with zero users by @jdx in
[#9725](#9725)
- add scalafmt
([github:scalameta/scalafmt](https://github.com/scalameta/scalafmt)) by
@pokir in [#9757](#9757)
- remove flarectl by @risu729 in
[#9756](#9756)

### Chore

- **(release)** strip pre-existing sponsor block before appending
canonical one by @jdx in [#9745](#9745)

### New Contributors

- @pokir made their first contribution in
[#9757](#9757)
@bfrengley

Copy link
Copy Markdown

@JamBalaya56562 this PR breaks bash resolution when a valid bash is first in PATH and also causes a valid windows_default_inline_shell_args set to an absolute path to a valid bash binary (so no PATH resolution needed) to be ignored in favour of git bash.

See #9932 for extra details

JamBalaya56562 added a commit to JamBalaya56562/mise that referenced this pull request May 31, 2026
On Windows an explicitly configured bash path (e.g. a task shell of
C:/msys64/usr/bin/bash.exe -c, or windows_default_inline_shell_args) was
ignored and silently re-resolved to a different bash such as Git Bash, via the
WSL-avoidance logic added in jdx#9750. Honor an explicit path (absolute, or
relative with a directory component) verbatim instead of re-resolving it.

Also unify how configured shell strings are parsed. A new split_shell_command
treats backslashes as literal path characters and only groups double-quoted
spans on Windows (Unix keeps shell_words / POSIX semantics), so shell paths
with spaces (when quoted) or backslashes survive instead of being mangled. It
is used for task shell, hook and watch_files shells, and the
*_default_*_shell_args settings. Task::shell() now returns a Result so a
malformed explicit shell (e.g. an unbalanced quote) fails loudly instead of
silently falling back to the default shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants