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
main still builds cmd = ["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 stub
git 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 the git.exe already 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
Summary
On Windows,
run_commit_hookspawns extensionless hooks as a bare"bash.exe"and leaves the executable lookup to Windows.CreateProcessresolves a bare name in this order: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:
GitPython surfaces this as
HookExecutionError, so everyrepo.index.commit()fails on such a machine and the hook body never runs — regardless of what the hook contains.Version
mainstill buildscmd = ["bash.exe", ...]Reproduction
git commitfrom 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
C:\Program Files\Git\binon PATHrun_commit_hooknever reads the shebangThe 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:
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
CreateProcesssearch order it is the one that gets selected by default, ahead of Git's own bash.Suggested fix
Resolve Git for Windows'
bash.exeby absolute path and use it explicitly, rejecting any candidate under%SystemRoot%. Candidate order that works well in practice:bin/directory beside thegit.exealready on PATH (shutil.which("git")→../bin/bash.exe)GIT_INSTALL_ROOTC:\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_popencall site ingit/index/fun.pyto rewrite a barebash.exe/sh.exeargv[0] to Git's absolute bash. Happy to open a PR againstrun_commit_hookif the approach above looks acceptable.Possibly related
WinError 193reports that motivated b719e18, the commit that introduced thebash.exespawn