Conversation
Greptile SummaryThis PR fixes Windows PATH conversion when mise spawns POSIX subshells (e.g. Confidence Score: 5/5This PR is safe to merge — no P0 or P1 issues found. The implementation is correct and well-scoped. The ordering of resolve_posix_shell_program_path before maybe_convert_env_for_msys_shell is critical (uses unconverted PATH to find bash.exe first) and is correct. The Cow-based helper is zero-cost on non-Windows and non-POSIX-shell paths. All unit tests cover the documented edge cases. The e2e test correctly uses $TestDrive (via Set-Location TestDrive: in BeforeAll) and cleans up properly in a finally block. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant PS as PowerShell
participant mise as mise (exec_program)
participant RP as resolve_posix_shell_program_path
participant MC as maybe_convert_env_for_msys_shell
participant W32 as Win32 CreateProcess
participant bash as bash subprocess
PS->>mise: mise run task (shell = "bash -c")
mise->>RP: ("bash", Windows-form PATH env)
RP-->>mise: C:\Program Files\Git\bin\bash.exe
mise->>MC: (bash.exe, Windows-form PATH env)
MC-->>mise: Cow::Owned(env with Unix-form PATH)
mise->>W32: spawn("C:\...\bash.exe", Unix PATH env)
W32->>bash: bash.exe started
bash-->>bash: PATH=/c/Users/...:/c/tools/bin (resolves tools ✓)
Reviews (6): Last reviewed commit: "Merge branch 'main' into windows-msys-pa..." | Re-trigger Greptile |
There was a problem hiding this comment.
Code Review
This pull request introduces automatic PATH conversion from Windows format to MSYS Unix format when spawning POSIX shells (such as bash, sh, or zsh) on Windows. It adds utility functions in src/path.rs to handle the conversion of drive letters and separators, integrates this logic into the task executor, and includes comprehensive unit and E2E tests. The review feedback suggests optimizing performance by avoiding environment cloning when no conversion is needed and improving the path conversion logic to handle relative paths containing backslashes.
…on Windows
When mise on Windows spawns a POSIX-style shell (`bash -c`, `sh -c`, ...)
for a task, the child receives PATH in Windows form (`C:\foo;D:\bar`).
bash uses `:` as the path separator, so it fails to resolve any command —
including tools mise has just installed for the task via per-task
`tools = { ... }`. PowerShell-launched mise inherits no MSYSTEM, so the
prior `MSYSTEM`-based detection (PR jdx#5581) cannot help here.
Convert PATH to Unix form (`/c/foo:/d/bar`) at the spawn boundary in
`task_executor::exec_program`, gated on the target program being a
recognized POSIX shell name. Pure Rust, no subprocess, no `cygpath`
fallback — narrow scope per maintainer guidance from jdx#3961 / jdx#5581 /
jdx#6633. Linux/macOS and Windows-native-shell tasks are unaffected.
Closes a long-standing class of bugs around per-task `tools = { ... }` +
`shell = "bash -c"` on Windows.
- windows_path_list_to_unix: write directly to a single output String, avoiding the intermediate Vec<String> + join allocation. The helper becomes append_single_windows_path_to_unix and writes char-by-char, removing the per-entry replace() allocation as well. - maybe_convert_env_for_msys_shell: only clone the env if PATH is actually present, using a let-chain to keep the happy path linear.
…dows Review fixes: - Skip the env clone when PATH is already in Unix form (no `;` separator and no `\` to translate). This is the common case when mise itself runs inside Git Bash and spawns another bash subshell — Cow stays Borrowed. - Translate `\` → `/` for non-canonical-drive, non-UNC entries too. POSIX shells do not treat `\` as a separator, so a relative entry like `node_modules\.bin` (which mise can inject via `[env] _.path`) was previously passed through unchanged and broke command resolution. Lint fix: - The pure-Rust path conversion lives in a generic module so unit tests build on all platforms, but the production callers are all gated on `#[cfg(windows)]`. On non-Windows non-test builds, that triggered `dead_code` via `clippy -- -D warnings`. Added `#[cfg_attr(not(windows), allow(dead_code))]` on the public symbols and the private helper instead of cfg-gating them, to keep the cross-platform unit-test surface intact. Tests: - New: relative paths with backslashes are translated. - New: env stays Cow::Borrowed when PATH is already Unix-form. - New: env stays Cow::Borrowed when PATH is absent entirely.
…n on Windows Without this, the Windows e2e suite's "shebang task with bash" test and the "converts PATH to MSYS Unix form" test both fail with empty output: bash never starts. Root cause: `Command::spawn` on Windows uses the *child* env's PATH (when set via `.envs(...)`) to locate the program. The PATH conversion was handing it `/c/foo:/d/bar`, which Win32 cannot use to find `bash.exe`, so CreateProcess failed before bash could run. Fix: when the program is a POSIX shell and the PATH would be converted, resolve the program to its absolute path first using the still-Windows- form PATH from the task env. The child then receives an absolute program argument and the converted Unix-form PATH — the spawn itself bypasses PATH search at the OS level, and bash sees a usable Unix PATH for its own command resolution. This affects both inline tasks (`shell = "bash -c"`) and shebang-driven file tasks (`#!/usr/bin/env bash`); both flow through `exec_program`, where the resolution is now performed.
71dd310 to
5632106
Compare
### 🚀 Features - **(conda)** graduate conda backend out of experimental by @jdx in [#9544](#9544) - **(deps)** Add dart and flutter providers by @tjarvstrand in [#9505](#9505) - **(registry)** add neo4j by @mnm364 in [#9525](#9525) - **(registry)** add rustfs by @mnm364 in [#9530](#9530) - **(task)** support exclusion patterns in task sources by @jlarmstrongiv in [#9496](#9496) - **(vfox)** add stat function to lua file module by @esteve in [#9497](#9497) ### 🐛 Bug Fixes - **(backend)** flag regex prerelease versions by @jdx in [#9500](#9500) - **(backend)** mark -nightly/-canary/-experimental as prereleases by @jdx in [#9523](#9523) - **(backend)** suppress no-versions warning for unresolved-latest backends by @jdx in [#9548](#9548) - **(backend)** include dotnet prereleases from package flags by @jdx in [#9551](#9551) - **(backend)** scope PEP 440 prerelease detection to Python backends by @jdx in [#9558](#9558) - **(cargo)** Apply install_env during cargo install by @c22 in [#9502](#9502) - **(copr)** drop epel-9 chroots since rust >= 1.91 is unavailable by @jdx in [#9484](#9484) - **(github)** skip attestations on non-default api_url by @jdx in [#9486](#9486) - **(github)** retry ip allow list errors without auth by @risu729 in [#9506](#9506) - **(http)** update versions host tracking endpoint by @jdx in [#9527](#9527) - **(install)** don't warn for configured tools when version is passed via CLI by @jdx in [#9522](#9522) - **(install)** refresh latest before installing missing tools by @jdx in [#9545](#9545) - **(install)** don't cache nonexistent install paths by @jdx in [#9553](#9553) - **(lockfile)** don't propagate ad-hoc CLI overrides into the project lockfile by @jdx in [#9562](#9562) - **(plugin)** detect plugin types after cloning by @risu729 in [#9540](#9540) - **(release)** pass --no-git-checks to aube publish by @jdx in [#9483](#9483) - **(task)** convert PATH to MSYS Unix form when spawning POSIX shells on Windows by @JamBalaya56562 in [#9547](#9547) ### 📚 Documentation - **(contributing)** require popularity check for registry PRs by @jdx in [7bbeebe](7bbeebe) - **(watch)** update pitchfork domain to en.dev by @risu729 in [#9536](#9536) - document ghtkn GitHub token setup by @jdx in [#9546](#9546) - clarify registry backend acceptance policy by @jdx in [#9543](#9543) - Change exec command to use bash for variable echo by @kuboon in [#9567](#9567) ### 🧪 Testing - **(e2e)** run test-tool targets in parallel by @jdx in [#9564](#9564) - **(e2e)** run tests in parallel by @jdx in [#9563](#9563) - **(e2e)** bind-mount /tmp on disk and surface failed tests in CI summary by @jdx in [#9570](#9570) - **(tasks)** migrate test_task_help atask to usage field by @jdx in [#9549](#9549) ### 📦️ Dependency Updates - update fedora:45 docker digest to 8b838b3 by @renovate[bot] in [#9507](#9507) - update ghcr.io/jdx/mise:deb docker digest to f02194c by @renovate[bot] in [#9509](#9509) - update taiki-e/install-action digest to 7769b73 by @renovate[bot] in [#9512](#9512) - update ghcr.io/jdx/mise:alpine docker digest to 581f8a8 by @renovate[bot] in [#9508](#9508) - update rust crate ctor to v0.10.1 by @renovate[bot] in [#9515](#9515) - update ghcr.io/jdx/mise:rpm docker digest to a5c9655 by @renovate[bot] in [#9510](#9510) - update rust docker digest to a9cfb75 by @renovate[bot] in [#9511](#9511) - update rust crate age to v0.11.3 by @renovate[bot] in [#9514](#9514) - update rust crate jiff to v0.2.24 by @renovate[bot] in [#9516](#9516) - update dependency vitepress-plugin-tabs to ^0.9.0 by @renovate[bot] in [#9518](#9518) - update autofix-ci/action action to v1.3.4 by @renovate[bot] in [#9513](#9513) - update rust crate usage-lib to v3.2.1 by @renovate[bot] in [#9517](#9517) - update apple-actions/import-codesign-certs action to v7 by @renovate[bot] in [#9519](#9519) - update taiki-e/install-action digest to 51cd0b8 by @renovate[bot] in [#9531](#9531) - exclude taiki-e/install-action from renovate by @jdx in [#9532](#9532) - update rust crate blake3 to v1.8.5 by @renovate[bot] in [#9533](#9533) ### 📦 Registry - enable shellcheck on windows by @zeitlinger in [#9487](#9487) - add google-java-format by @zeitlinger in [#9488](#9488) - add expert ([aqua:expert-lsp/expert](https://github.com/expert-lsp/expert)) by @AlternateRT in [#9498](#9498) - update entry for checkmake by @eread in [#9504](#9504) - add systemctl-tui ([aqua:rgwood/systemctl-tui](https://github.com/rgwood/systemctl-tui)) by @2xdevv in [#9521](#9521) - add codon by @3w36zj6 in [#9538](#9538) - add tool yr (backend:github:VirusTotal/yara-x) by @adam-moss in [#9542](#9542) - add tool betterleaks (backend:aqua/betterleaks/betterleaks) by @adam-moss in [#9541](#9541) - add `git-filter-repo` by @garysassano in [#9550](#9550) - add umoci ([aqua:opencontainers/umoci](https://github.com/opencontainers/umoci)) by @2xdevv in [#9555](#9555) - add aqua backend for elixir-ls by @AlternateRT in [#9557](#9557) - deny inline backend options by @risu729 in [#9565](#9565) ### Chore - **(ci)** fail registry tests without summary by @jdx in [#9559](#9559) - **(ci)** use !cancelled() instead of always() for test-ci aggregator by @jdx in [#9569](#9569) - **(ci)** use namespace runners for ci jobs by @jdx in [#9561](#9561) - **(config)** deprecate shorthands_file setting by @risu729 in [#9534](#9534) - **(docs)** remove shrill.en.dev analytics script by @jdx in [#9539](#9539) - **(release)** replace bc with awk in release-plz star formatting by @jdx in [d7f177f](d7f177f) - bump hk to 1.44.3 by @jdx in [#9493](#9493) - invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by @jdx in [#9560](#9560) - set dev profile debug to 1 by @jdx in [#9572](#9572) ### New Contributors - @kuboon made their first contribution in [#9567](#9567) - @AlternateRT made their first contribution in [#9557](#9557) - @2xdevv made their first contribution in [#9555](#9555) - @adam-moss made their first contribution in [#9541](#9541) - @jlarmstrongiv made their first contribution in [#9496](#9496) - @tjarvstrand made their first contribution in [#9505](#9505)
Maintainer context — scope and prior decisions
This PR addresses the long-standing class of bugs around mise + Windows + POSIX subshells discussed in #3961, #5581, #6633. Per maintainer feedback on those threads:
Accepted:
cygpathsubprocess (Command::new("cygpath")).Rejected on past PRs (and avoided here):
cygpath.MSYSTEMenv trigger (the PowerShell→mise→bash path has noMSYSTEM).Distinct from already-merged #4048: that PR fixed
mise activate bashoutput when mise itself runs inside Git Bash. This PR fixes the orthogonal case — mise spawning a POSIX subshell from any host shell.Summary
crate::path::windows_path_list_to_unix(pure-Rust;-separated →:-separated, drive-letterC:\→/c/, UNC pass-through).crate::path::is_posix_shell_program(basename match againstbash/sh/zsh/fish/ksh/dash, case-insensitive,.exestripped).task_executor::exec_program, just beforeCmdLineRunner::envs(...), callmaybe_convert_env_for_msys_shell(program, env). On Windows + recognized POSIX shell only, clone the env and rewrite thePATHentry; otherwise returnCow::Borrowed(zero allocation).The cfg-gated
Cowpattern keeps the call site OS-agnostic and adds zero cost to Linux/macOS or to Windows-native-shell tasks.Design choices
/c/only (Git Bash style)/cygdrive/c/users typically don't go throughbash -cfrom PowerShell. Configurable setting would be scope creep.task_executor::exec_program, env-passing site only.src/shell/*.rsandhook_env.rsuntouched.mise runpath, notmise activate(that's PR #5581's scope). Single chokepoint, no shell-escape concerns.bash/sh/zsh/...), notMSYSTEMenvMSYSTEM-trigger misses the PowerShell→mise→bash chain entirely. Program-name trigger covers shebang dispatch (#!/usr/bin/env bash) too.task.shell()resolves to a program path before reachingexec_program; we read it there.Out of scope (intentional)
/etc/fstabmount table.\?\C:\...,\server\share\...) — passed through verbatim, bash will fail to use them, matches no-conversion behavior./cygdrive/c/prefix./usrmagic mount —/c/Program Files/Git/usr/binresolves to the same exe via PATH search, no remap needed.windows_path_list_to_unixdoes not provide the reverse direction (-wp); not currently needed.Tests
Unit tests (
src/path.rs+src/task/task_executor.rs, all platforms):windows_path_list_to_unix: basic, forward-slash input, mixed separators, Unix entry pass-through, UNC pass-through, empty entries (leading/trailing/sole;), drive-letter case folding,Program Fileswith spaces, bareC:/C:foopass-through, single entry — 11 cases.is_posix_shell_program: bare names,.exesuffix, full Windows paths, Unix paths,BASH.EXEuppercase, negative cases (cmd,powershell,pwsh,rustc, empty) — 1 case with all assertions.maybe_convert_env_for_msys_shell(Windows-only): converts forbash.exe, skips forcmd.exe, full path tobash.exe, plus a Unix-side no-op test — 3 Windows + 1 Unix.Integration test (
e2e-win/task.Tests.ps1): a Pester case that writes amise.tomlwithshell = "bash -c", runs the task from PowerShell, and asserts the PATH the task observes contains:(Unix-style) rather than;(Windows-style). Skips gracefully ifbash.exeisn't on PATH.Local verification
cargo test -p mise --bin mise -- path::tests:: task::task_executor::tests::cargo check --workspace --message-format=shortcargo fmt -p mise --checkcargo clippy -- -Dwarnings,cargo build --bin mise,pwshrepro, e2e-win Pestergetrandom v0.3raw-dylibrequiresdlltoolthat the local MSVC toolchain didn't provide. Deferred to CI.Acceptance criterion
The repro from #3961:
…run from PowerShell as
mise run repro_build_rustshould resolverustupandcargoinside the bash subshell. Bug-source code path verified by unit tests; final repro to be confirmed by CI on this PR.This PR description was authored with the assistance of an AI coding assistant.