Repository navigation
Conversation
On Windows, `run_commit_hook` runs an extensionless hook through Bash and passes the hook's path relative to the working tree (`os.path.relpath`). Bash resolves that path in its own view of the filesystem, which differs from the Windows one across a mount point. Git for Windows mounts the user's temporary directory as `/tmp` (`none /tmp usertemp` in its `/etc/fstab`). For a repository under `%TEMP%` and a `core.hooksPath` outside it, such as a global `~/.config/git/hooks`, Bash starts in `/tmp/tmpXXXX/repo`, and `../../../../../.config/git/hooks/pre-commit`, correct on the Windows side, leads to `/.config/git/hooks/pre-commit`. Every commit then fails with exit code 127 and `No such file or directory`. A relative path that stays inside the working tree cannot cross such a mount, so it is kept. A path that leaves the working tree (one starting with `..`) is now passed as the absolute path in POSIX form (`C:/...`), as is already done for a hook on another drive, when the Bash is Git for Windows' own or one found on `PATH`: both accept it. The bare `bash.exe` fallback may be the WSL launcher, which cannot open a Windows absolute path, so it keeps the relative path as before. `test_run_commit_hook_outside_worktree_on_windows` now checks the three cases (outside the working tree with a found Bash, inside it, outside it with the bare fallback), and `test_pre_commit_hook_outside_worktree` runs a real hook through a `core.hooksPath` outside the working tree. Validated on Windows 11 with Git for Windows 2.50.1 and Python 3.14: `test/test_index.py` passes with an empty global Git configuration (183 passed, 3 skipped, 1 xpassed), and the new command-construction assertion fails without the change; `ruff` 0.16.9 and `mypy` report no issues on the changed files; a commit in a repository under `%TEMP%`, with a global `core.hooksPath` holding a `pre-commit` hook, succeeds instead of failing with exit code 127. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused fix preserves existing fallback behavior, includes regression coverage, and has no identified blocking issues.
0 open findings
What changed in this PR
This PR fixes Windows hook execution when core.hooksPath points outside a working tree under %TEMP%, addressing #2287.
Review by GitHub Copilot.
Changes:
- Pass outside-worktree hooks by absolute POSIX-form path to discovered Bash executables.
- Preserve relative paths for internal hooks and the bare
bash.exefallback. - Expand command-construction coverage and add an external-hook integration test.
| File | Description |
|---|---|
test/test_index.py |
Tests path selection and external-hook execution. |
git/index/fun.py |
Adjusts Windows hook paths while preserving fallback behavior. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Member
|
Double checked with Codex on Windows, and WSL support as well. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2287.
On Windows, a hook found through a
core.hooksPathoutside the working tree is passed to Bash by a path relative to the working tree. Git for Windows mounts the user's temporary directory as/tmp, so from a repository under%TEMP%that relative path leads elsewhere in Bash, and the hook fails with exit code 127 (details in #2287).Change
In
run_commit_hook(git/index/fun.py):..) is replaced by the absolute path in POSIX form (C:/...), as is already done for a hook on another drive, when the Bash is Git for Windows' own or one found onPATH: both accept it.bash.exefallback, which may be the WSL launcher and cannot open a Windows absolute path, keeps the relative path as before.Tests
test_run_commit_hook_outside_worktree_on_windowsnow checks the command for the three cases. Its first assertion (outside the working tree, Git for Windows' Bash) fails without the change.test_pre_commit_hook_outside_worktreeruns a real hook through acore.hooksPathoutside the working tree.Validation
On Windows 11 with Git for Windows 2.50.1 and Python 3.14:
test/test_index.pywith an empty global Git configuration: 183 passed, 3 skipped, 1 xpassed (182 passed onmain; the xpass istest_commit_msg_hook_success, as onmain).ruff0.16.9 (check and format) andmypyon the changed files: no issues.%TEMP%with a globalcore.hooksPathholding apre-commithook succeeds; onmainit fails with exit code 127.Not run here: Linux and macOS (the command-construction test mocks
sys.platform, so CI runs it there too) and a WSLbash.exe.A related observation, outside the scope of this PR: with a global
core.hooksPathset, some hook tests intest/test_index.py(for exampletest_pre_commit_hook_failandtest_commit_msg_hook_fail) fail even with this change, because they expect the hook from the repository's ownhooksdirectory while Git's configuration points elsewhere. They pass with an empty global configuration.🤖 Generated with Claude Code