surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| 1 | #!/usr/bin/env bash |
| 2 | # Find a working Python 3 interpreter and exec the hook with it. |
| 3 | # |
| 4 | # On Windows + Git Bash, `python3` typically resolves to the Microsoft Store |
| 5 | # stub at C:\Users\<user>\AppData\Local\Microsoft\WindowsApps\python3, which |
| 6 | # exits 49 silently in non-TTY subprocess context (a known Microsoft Store |
| 7 | # stub behavior). This shim |
| 8 | # probes each candidate with `-c ""` and skips any that fails, so the Store |
| 9 | # stub falls through to the real python.org install (`python` in Git Bash) or |
| 10 | # the `py -3` launcher. |
| 11 | # |
| 12 | # Order: |
| 13 | # 1. python3 — canonical on macOS/Linux; the Store stub fails the probe. |
| 14 | # 2. python — python.org installs on Windows; some Linux distros (RHEL 7 |
| 15 | # EOL'd 2024-06) point this at Python 2, but `-c ""` succeeds |
| 16 | # on Python 2 too — guard with a version check. |
| 17 | # 3. py -3 — Windows Python launcher. |
| 18 | # |
| 19 | # Args after the shim path are passed straight through to the chosen |
| 20 | # interpreter, so the hooks.json invocation is: |
| 21 | # bash "${CLAUDE_PLUGIN_ROOT}/hooks/sg-python.sh" \ |
| 22 | # "${CLAUDE_PLUGIN_ROOT}/hooks/security_reminder_hook.py" |
| 23 | set -e |
| 24 | |
| 25 | # Force UTF-8 for ALL Python filesystem + IO operations (PEP 540). |
| 26 | # Without this, Windows Python defaults `locale.getpreferredencoding()` to |
| 27 | # cp1252 — which makes `text=True` in subprocess.run / open() / json.load |
| 28 | # crash the internal reader thread on any byte that's undefined in cp1252 |
| 29 | # (e.g. the 0x81 byte from ف, present in any path/filename with |
| 30 | # Arabic/Hebrew/CJK characters). See #2056, #2099. |
| 31 | # |
| 32 | # No-op on macOS/Linux (already UTF-8). Must be set BEFORE Python starts — |
| 33 | # changing it from inside the interpreter has no effect. |
| 34 | export PYTHONUTF8=1 |
| 35 | |
| 36 | # Git Bash / MSYS on Windows hands script paths to this shim in POSIX form |
| 37 | # (`/c/Users/...`). When we exec a Windows `python.exe` (which we do on |
| 38 | # Windows since `python3` is the Microsoft Store stub), python interprets the |
| 39 | # leading `/` as the root of the current drive — e.g. `/c/Users/...` becomes |
| 40 | # `C:\c\Users\...` or `D:\c\Users\...` (whichever drive the shell is on), |
| 41 | # fails with ENOENT, and every Edit/Write/MultiEdit tool use blocks until the |
| 42 | # session restarts. See anthropics/claude-plugins-official#2043. |
| 43 | # |
| 44 | # Fix: convert absolute path args to native Windows form via `cygpath -w` |
| 45 | # before exec. `cygpath` is a Git Bash builtin; it's absent on macOS/Linux, |
| 46 | # where the `command -v` guard makes this a no-op. `cygpath -w` is idempotent |
| 47 | # for already-Windows paths so the rare mixed-form case is safe. |
| 48 | if command -v cygpath >/dev/null 2>&1; then |
| 49 | converted=() |
| 50 | for a in "$@"; do |
| 51 | case "$a" in |
| 52 | /*) converted+=("$(cygpath -w "$a")") ;; |
| 53 | *) converted+=("$a") ;; |
| 54 | esac |
| 55 | done |
| 56 | set -- "${converted[@]}" |
| 57 | fi |
| 58 | |
| 59 | probe() { |
| 60 | # $1..N: the interpreter command (may be multi-word like `py -3`) |
| 61 | # Writes "<major>.<minor>" to stdout and exits 0 iff at least Python 3. |
| 62 | "$@" -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")' 2>/dev/null |
| 63 | } |
| 64 | |
| 65 | # True iff arg is a "M.m" version string >= 3.10. claude_agent_sdk requires |
| 66 | # Python >= 3.10; below that, pip install fails ("No matching distribution") |
| 67 | # and the LLM-powered review (Stop / commit / push) silently no-ops while |
| 68 | # pattern checks (PostToolUse regex) keep working. macOS ships 3.9.6 as the |
| 69 | # default `python3` on current versions, so this guard matters in practice. |
| 70 | # See anthropics/claude-plugins-official#2071. |
| 71 | is_sdk_compatible() { |
| 72 | case "$1" in |
| 73 | 3.1[0-9]|3.[2-9][0-9]|[4-9].*|[1-9][0-9].*) return 0 ;; |
| 74 | *) return 1 ;; |
| 75 | esac |
| 76 | } |
| 77 | |
| 78 | # Pass 1 — try minor-versioned binaries in descending order. These are only |
| 79 | # present if the user explicitly installed them (Homebrew / python.org / pyenv), |
| 80 | # so picking one here always upgrades over the system `python3`. Highest |
| 81 | # available wins; the user doesn't have to PATH-prefer it. |
| 82 | for cmd in "python3.13" "python3.12" "python3.11" "python3.10"; do |
| 83 | v=$(probe "$cmd") || continue |
| 84 | if is_sdk_compatible "$v"; then |
| 85 | exec "$cmd" "$@" |
| 86 | fi |
| 87 | done |
| 88 | |
| 89 | # Pass 2 — bare interpreters, but only if SDK-compatible. Covers Linux distros |
| 90 | # that ship 3.10+ as the default `python3`, and Windows where `python` / |
| 91 | # `py -3` resolves to the user's python.org install. |
| 92 | for cmd in "python3" "python" "py -3"; do |
| 93 | # shellcheck disable=SC2086 |
| 94 | v=$(probe $cmd) || continue |
| 95 | if is_sdk_compatible "$v"; then |
| 96 | # shellcheck disable=SC2086 |
| 97 | exec $cmd "$@" |
| 98 | fi |
| 99 | done |
| 100 | |
| 101 | # Pass 3 — fallback to any Python 3, even <3.10. Pattern-based checks |
| 102 | # (PostToolUse regex on Edit/Write) only need 3.6+ and are useful on their |
| 103 | # own; the SDK-dependent paths will detect the version mismatch and degrade |
| 104 | # inside the Python code. Without this fallback, the entire plugin would |
| 105 | # stop working on default macOS, which is a regression vs today. |
| 106 | for cmd in "python3" "python" "py -3"; do |
| 107 | # shellcheck disable=SC2086 |
| 108 | v=$(probe $cmd) || continue |
| 109 | # Accept anything that successfully reported a "M.m" string. |
| 110 | case "$v" in |
| 111 | [0-9]*.[0-9]*) |
| 112 | # shellcheck disable=SC2086 |
| 113 | exec $cmd "$@" |
| 114 | ;; |
| 115 | esac |
| 116 | done |
| 117 | |
| 118 | echo "security-guidance: no working Python 3 interpreter found." >&2 |
| 119 | echo " tried: python3.13, python3.12, python3.11, python3.10, python3, python, py -3" >&2 |
| 120 | echo " on Windows, install Python from https://python.org (NOT the Microsoft Store)" >&2 |
| 121 | echo " on macOS, install Python 3.10+ via Homebrew (\`brew install python\`)" >&2 |
| 122 | exit 1 |