Skip to content

run_commit_hook picks the WSL launcher stub on Windows: bare bash.exe resolves from System32 before PATH #2198

Description

@piyushmakhija28

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:

  1. the bin/ directory beside the git.exe already on PATH (shutil.which("git")../bin/bash.exe)
  2. GIT_INSTALL_ROOT
  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions