[Windows/Git Bash] PreToolUse hook enforcement silently bypassed — hooks exit 49 with no output or warning when python3 resolves to Store stub
Why this is not a duplicate of #15908
#15908 and #46449 describe python3 failing with a visible "Python not found" error in PowerShell/CMD. Users see the error and know something is wrong.
This report is a distinct failure mode in a different shell:
| | #15908 / #46449 | This report |
|---|---|---|
| Shell | PowerShell / CMD | Git Bash (Claude Code Bash tool subprocess) |
| which python3 | May fail or show Store message | Succeeds — stub is on PATH |
| Failure visible? | Yes — "Python not found" shown | No — hook exits silently in subprocess context |
| Exit code | Not documented | 49 (Store redirect code) |
| Claude Code behavior | Shows hook error | No error, no warning — proceeds |
| Scope | security-guidance plugin | All user-written enforcement hooks |
In an interactive terminal, python3 does print the "Python was not found" message (see screenshot). In the non-TTY subprocess context where Claude Code runs hooks, the Store stub suppresses that message and exits 49 silently. No user-facing signal anywhere.
<img width="1273" height="650" alt="Image" src="https://github.com/user-attachments/assets/18bd5f89-dbb9-4de2-a921-6979db7a72df" />
---
Environment
- OS: Windows 11 Home 10.0.26200
- Claude Code: 2.1.138
- Shell (Bash tool subprocess): Git Bash
- Python: 3.13.12 installed via python.org
- Hooks framework: User-written PreToolUse enforcement hooks in
.claude/settings.json
---
The Problem
On Windows 11 with Python installed via python.org, python3 in a Git Bash subprocess resolves to the Windows App Execution Alias stub at:
C:\Users\<user>\AppData\Local\Microsoft\WindowsApps\python3
This stub is a Microsoft Store redirect shim. In an interactive terminal it prints "Python was not found..." and exits 49. In the non-TTY subprocess context where Claude Code runs hooks, it produces no output at all and exits 49.
Claude Code treats exit code 49 from a PreToolUse hook as non-blocking. The hook subprocess exits 49, Claude Code proceeds, and the tool call executes as if the hook never ran.
Result: every enforcement hook configured as python3 <hook>.py silently fails in the hook subprocess. The user has no indication their enforcement layer is not running.
---
Reproduction
Step 1 — Confirm the stub behavior (interactive terminal)

which python3
# /c/Users/<user>/AppData/Local/Microsoft/WindowsApps/python3
python3 --version
# Python was not found; run without arguments to install from the Microsoft Store,
# or disable this shortcut from Settings > Apps > Advanced app settings > App execution aliases.
echo $?
# 49
which python
# /c/Users/<user>/AppData/Local/Programs/Python/Python313/python
python --version
# Python 3.13.12
Step 2 — Verify exit 49 is non-blocking in hook subprocess
Add a minimal PreToolUse Bash hook that exits 49 on a sentinel command:
# probe.py
import sys, json
data = json.load(sys.stdin)
if "EXIT49-PROBE" in data.get("tool_input", {}).get("command", ""):
sys.exit(49)
sys.exit(0)
Wire it in .claude/settings.json PreToolUse Bash hooks:
{"type": "command", "command": "python probe.py"}
Trigger from Claude Code:
echo "EXIT49-PROBE"
Observed: Command executes and returns output. Exit 49 did not block the tool call.
Confirmed on: Claude Code 2.1.138, Windows 11, Git Bash.
---
Impact
On an affected Windows system where enforcement hooks use python3:
which python3succeeds → hooks appear configured correctly- Claude Code runs
python3 <enforcement-hook>.py - Store stub intercepts → exits 49, no output in subprocess context
- Claude Code treats exit 49 as non-blocking → tool call proceeds
- Enforcement is silently bypassed
- No error surfaced to user
This affects all user-written enforcement hooks using python3 — not just the security-guidance plugin. Users building custom enforcement workflows (pre-commit checks, write guards, dangerous command blocks) have no indication their hooks are not running.
---
Contrast with exit 127
Claude Code treats exit 127 as non-blocking by design — it signals "Python not installed." Exit 49 is also treated as non-blocking, but comes from a binary that was found. The distinction matters for detection:
- Exit 127:
python3not on PATH →which python3fails → diagnosable - Exit 49:
python3on PATH but is a Store stub →which python3succeeds → false signal of correct configuration
---
Workaround
Use python instead of python3 in .claude/settings.json hook definitions:
// Before (silently broken on Windows + Git Bash):
{"type": "command", "command": "python3 /absolute/path/to/hook.py"}
// After (works):
{"type": "command", "command": "python /absolute/path/to/hook.py"}
Note: python3 works correctly in PowerShell via Windows App Execution Aliases — the failure is specific to the Git Bash subprocess context Claude Code uses for the Bash tool.
---
Suggested Fix
At startup or hook pre-flight, Claude Code could detect the stub:
python3 -c "import sys; sys.exit(0)"
# If exit code != 0: warn that python3 is not resolving to a real interpreter
# and hook enforcement may be silently bypassed
Alternatively, a Windows-specific note in the hooks documentation that python3 should not be used in Git Bash hook definitions.
---
Related
- #15908 — python3 fails with visible error in PowerShell (security-guidance plugin) — closed
- #46449 — duplicate of #15908 — closed
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗