Repository navigation
run_commit_hook picks the WSL launcher stub on Windows: bare bash.exe resolves from System32 before PATH #2198
Description
Summary
On Windows, run_commit_hook spawns extensionless hooks as a bare "bash.exe" and leaves the executable lookup to Windows. CreateProcess resolves a bare name in this order:
application dir -> parent cwd -> System32 -> Windows dir -> PATH
so C:\Windows\System32\bash.exe — the WSL launcher stub, present on any machine where the "Windows Subsystem for Linux" optional feature has been enabled — is found at step 3, before PATH is ever consulted.
If no WSL distro is installed, that stub prints and exits 1:
Windows Subsystem for Linux has no installed distributions.
You can resolve this by installing a distribution with the instructions below:
Use 'wsl.exe --list --online' to list available distributions
and 'wsl.exe --install <Distro>' to install.
GitPython surfaces this as HookExecutionError, so every repo.index.commit() fails on such a machine and the hook body never runs — regardless of what the hook contains.
Version
- GitPython 3.1.46 (reproduced), and current
mainstill buildscmd = ["bash.exe", ...] - Windows 11, Git for Windows 2.x, WSL optional feature enabled with no distro installed
Reproduction
from pathlib import Path
import git
repo = git.Repo.init("scratch")
hook = Path("scratch/.git/hooks/pre-commit")
hook.write_text("#!/bin/sh\necho HOOK RAN\nexit 0\n", newline="\n")
hook.chmod(0o755)
Path("scratch/f.txt").write_text("x\n")
repo.index.add(["f.txt"])
repo.index.commit("test") # HookExecutionError on a machine with the WSL stubgit commit from Git Bash on the same repo runs the hook fine, which makes this look like a hook or repo problem rather than a library one.
Why the usual workarounds don't apply
| Workaround | Why it cannot help |
|---|---|
Put C:\Program Files\Git\bin on PATH |
PATH is consulted only after System32 |
| Give the hook an absolute-path shebang | run_commit_hook never reads the shebang |
| Reorder PATH ahead of System32 | Irrelevant — System32 is searched before PATH regardless of PATH contents |
The only thing that changes the outcome is naming the interpreter by absolute path.
Note on the current comment in the source
The relative-path calculation carries this comment:
Different drives have no relative path on Windows. Git Bash accepts an absolute path in this form, although a relative path is preferable because it also works with the Windows Subsystem for Linux wrapper.
That reads as though WSL bash is an acceptable target. When WSL is a stub with no distro it is not merely acceptable-but-different, it is guaranteed to fail — and because of the CreateProcess search order it is the one that gets selected by default, ahead of Git's own bash.
Suggested fix
Resolve Git for Windows' bash.exe by absolute path and use it explicitly, rejecting any candidate under %SystemRoot%. Candidate order that works well in practice:
- the
bin/directory beside thegit.exealready on PATH (shutil.which("git")→../bin/bash.exe) GIT_INSTALL_ROOT- standard install locations (
C:\Program Files\Git,C:\Program Files (x86)\Git)
Falling back to today's bare "bash.exe" when none is found would keep behavior unchanged for anyone currently relying on WSL bash.
I am working around it downstream by patching the single safer_popen call site in git/index/fun.py to rewrite a bare bash.exe / sh.exe argv[0] to Git's absolute bash. Happy to open a PR against run_commit_hook if the approach above looks acceptable.
Possibly related
- [Windows] Hooks for submodules that are configured outside the submodule repository lead to error in PurePath.relative_to #1999 (closed, not planned) — a different failure in the same Windows hook path
- OSError: [WinError 193] %1 is not a valid Win32 application when commiting LFS-enabled repository on Windows #971 / Issue while trying to commit - "repo.index.commit" #1129 — the
WinError 193reports that motivated b719e18, the commit that introduced thebash.exespawn
Activity
Resolve Windows hook Bash through PATH (#2198)