[Windows/Git Bash] PreToolUse hook enforcement silently bypassed — hooks exit 49 with no output or warning when python3 resolves to Store stub

Resolved 💬 1 comment Opened May 11, 2026 by Dwink213 Closed Jun 9, 2026

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 → WindowsApps stub, python3 --version → Store message, echo 0 → 49, which python → real Python313, python --version → Python 3.13.12](screenshot)

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:

  1. which python3 succeeds → hooks appear configured correctly
  2. Claude Code runs python3 <enforcement-hook>.py
  3. Store stub intercepts → exits 49, no output in subprocess context
  4. Claude Code treats exit 49 as non-blocking → tool call proceeds
  5. Enforcement is silently bypassed
  6. 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: python3 not on PATH → which python3 fails → diagnosable
  • Exit 49: python3 on PATH but is a Store stub → which python3 succeeds → 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

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗