On Windows with MSYS2 and WSL, sh_binary targets fail at runtime when the source script uses #!/usr/bin/env bash.
Environment: Windows, MSYS2 (BAZEL_SH=C:\msys64\usr\bin\bash.exe), WSL installed, Bazel 9.x, rules_shell 0.8.0
Reproduction
git clone https://github.com/bazelbuild/rules_jvm_external.git
cd rules_jvm_external
echo 9.1.0 > .bazelversion
bazel run @duplicate_version_warning//:pin
/bin/bash: D:/.../external/+maven+duplicate_version_warning/pin: No such file or directory
Any sh_binary with a #!/usr/bin/env bash shebang will reproduce this.
Cause
The .exe launcher invokes bash.exe -c "path/to/script args...". MSYS bash execs the script, and the MSYS runtime reads the shebang. #!/usr/bin/env bash causes env to search the MSYS PATH, where /c/WINDOWS/system32/bash (WSL) appears before /usr/bin/bash (MSYS):
$ C:\msys64\usr\bin\bash.exe -c "which -a bash"
/c/WINDOWS/system32/bash ← WSL (found first)
/usr/bin/bash ← MSYS (correct, but second)
WSL bash cannot resolve Windows-style paths → "No such file or directory".
Both use_bash_launcher code paths are affected:
False (default): source is symlinked as-is, preserving the shebang.
True: wrapper has shebang = "" but calls exec "$(rlocation ...)" "$@", which triggers shebang processing of the underlying script.
Why Bazel 9 only
sh_binary.bzl uses getattr(native, "sh_binary", _sh_binary). In Bazel 8, the native implementation was used (no .exe launcher). In Bazel 9, native rules were removed (#23043), so the Starlark implementation creates the .exe launcher, exposing this interaction.
Suggested fix
The shebang is redundant on Windows — the .exe launcher already resolves bash via bash_bin_path from the sh_toolchain.
use_bash_launcher=False on Windows: Instead of symlinking, copy the script and strip #!/usr/bin/env shebangs.
use_bash_launcher=True on Windows: Replace exec "$(rlocation ...)" "$@" with . "$(rlocation ...)" or bash "$(rlocation ...)" "$@" to avoid shebang re-processing.
Alternatively, changing bash_launcher.cc to invoke bash script args instead of bash -c "script args" would fix this at the Bazel core level (bash doesn't process shebangs for file arguments).
Workaround
Change shebangs from #!/usr/bin/env bash to #!/bin/bash. MSYS2 resolves /bin/bash directly to C:\msys64\usr\bin\bash.exe via its filesystem mount, bypassing PATH.
On Windows with MSYS2 and WSL,
sh_binarytargets fail at runtime when the source script uses#!/usr/bin/env bash.Environment: Windows, MSYS2 (
BAZEL_SH=C:\msys64\usr\bin\bash.exe), WSL installed, Bazel 9.x, rules_shell 0.8.0Reproduction
Any
sh_binarywith a#!/usr/bin/env bashshebang will reproduce this.Cause
The
.exelauncher invokesbash.exe -c "path/to/script args...". MSYS bashexecs the script, and the MSYS runtime reads the shebang.#!/usr/bin/env bashcausesenvto search the MSYS PATH, where/c/WINDOWS/system32/bash(WSL) appears before/usr/bin/bash(MSYS):WSL bash cannot resolve Windows-style paths → "No such file or directory".
Both
use_bash_launchercode paths are affected:False(default): source is symlinked as-is, preserving the shebang.True: wrapper hasshebang = ""but callsexec "$(rlocation ...)" "$@", which triggers shebang processing of the underlying script.Why Bazel 9 only
sh_binary.bzlusesgetattr(native, "sh_binary", _sh_binary). In Bazel 8, the native implementation was used (no.exelauncher). In Bazel 9, native rules were removed (#23043), so the Starlark implementation creates the.exelauncher, exposing this interaction.Suggested fix
The shebang is redundant on Windows — the
.exelauncher already resolves bash viabash_bin_pathfrom thesh_toolchain.use_bash_launcher=Falseon Windows: Instead of symlinking, copy the script and strip#!/usr/bin/envshebangs.use_bash_launcher=Trueon Windows: Replaceexec "$(rlocation ...)" "$@"with. "$(rlocation ...)"orbash "$(rlocation ...)" "$@"to avoid shebang re-processing.Alternatively, changing
bash_launcher.ccto invokebash script argsinstead ofbash -c "script args"would fix this at the Bazel core level (bash doesn't process shebangs for file arguments).Workaround
Change shebangs from
#!/usr/bin/env bashto#!/bin/bash. MSYS2 resolves/bin/bashdirectly toC:\msys64\usr\bin\bash.exevia its filesystem mount, bypassing PATH.