bazel-contrib / bazel-contrib/rules_shell

`sh_binary` fails on Windows with `#!/usr/bin/env bash` shebang when WSL is installed

Open
#59 7 comments 0 reactions 0 assignees View on GitHub
Dominant language
Starlark
Stars
27
Forks
13
PR merge metrics
No merged PRs in 30d

Description

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 `exec`s 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`.

1. **`use_bash_launcher=False` on Windows**: Instead of symlinking, copy the script and strip `#!/usr/bin/env` shebangs.
2. **`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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.