Fixes anthropics/claude-plugins-official#2071 — on macOS where the
default `python3` is Apple's Command Line Tools Python 3.9.6, the
plugin's agentic commit reviewer silently does not run, even when the
user has a newer Python installed.
Three compounding factors in the bug:
1. `sg-python.sh` only checks the major version (`3`), so it always
picks 3.9 even when 3.10+ is on PATH.
2. `claude_agent_sdk` requires Python >=3.10 — pip install on 3.9
returns "No matching distribution" -> bootstrap returns BUILD_FAILED.
3. Even with a hand-built 3.12 venv, `llm.py` imports the SDK
in-process into the hook's interpreter (still 3.9), which raises
SyntaxError. The existing venv-probe in `ensure_agent_sdk.py` uses
the venv's own Python (3.12) so it reports NOOP_VENV (healthy) while
the consumer fails — misleading telemetry on top of silent feature
degradation.
Per BQ telemetry, 14,073 external macOS users hit
sdk_bootstrap=BUILD_FAILED in the past 4 days (the default-macOS
cohort), out of ~86K total external installed users. Combined with
~20K other users in similar broken-bootstrap states (Windows pre-#2055,
Linux <3.10), about half the installed base has a silently-broken
agentic reviewer.
This PR implements the reporter's items #1, #3, and #4. Item #2
(running the SDK out-of-process) is deferred as a bigger refactor.
Item #1 — hooks/sg-python.sh — prefer >=3.10 binaries via 3-pass probe:
Pass 1: python3.13 / 3.12 / 3.11 / 3.10 (>=3.10 by name, highest wins)
Pass 2: bare python3 / python / py -3 (accept only if reported >=3.10)
Pass 3: bare python3 / python / py -3 (any Python 3, FALLBACK so
pattern checks still work on macOS-default 3.9 — no regression
vs today; SDK-dependent paths detect the version mismatch
inside Python and degrade cleanly via item #4)
Item #4 — ensure_agent_sdk.py — health-check honesty:
Added HOOK_PY_INCOMPATIBLE=6 outcome with short-circuit at top of main():
if sys.version_info < (3, 10):
return HOOK_PY_INCOMPATIBLE, "hook_py", f"py_{...}"
Telemetry consequences after rollout: sdk_bootstrap=6 is a new clean
bucket; some users currently miscounted in sdk_bootstrap=3 BUILD_FAILED
(wasted pip cycles) and sdk_bootstrap=1 NOOP_VENV (falsely-healthy)
move to sdk_bootstrap=6. The remaining NOOP_VENV count becomes
trustworthy.
Item #3 — ensure_agent_sdk.py — one-time user-visible notice:
When outcome == HOOK_PY_INCOMPATIBLE and a marker file at
`~/.claude/security/.agentic_unavailable_notice_v<pv>` doesn't exist,
the SessionStart response includes hookSpecificOutput.additionalContext
+ systemMessage explaining the situation. Marker file is plugin-
version-keyed so a future fix (e.g. shipping out-of-process SDK) can
bump pv and re-notify users.
BUILD_FAILED is intentionally excluded from the notice — it covers
transient causes where a permanent banner would mislead.
Verified locally on macOS Python 3.13:
- py_compile clean on both files.
- Existing 45-test smoke + extensibility suite: 45/45 PASS in 2.50s.
- Unit test of simulated 3.9 path: HOOK_PY_INCOMPATIBLE returned with
correct phase/kind; notice shown on first call, suppressed on
second, reshown on bumped pv; BUILD_FAILED correctly does NOT
trigger notice.
NOT verified: actual Python 3.9 behavior end-to-end (would need a 3.9
install). Worth a follow-up smoke test in a 3.9 venv before next
release. The unit test simulating 3.9 covers the logic but not the
runtime invocation through the shim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes#2043. On Git Bash for Windows, Claude Code hands script paths to
the shim in POSIX form (`/c/Users/...`). We exec a Windows `python.exe`
(the `python3` Microsoft Store stub fails the probe), and Windows Python
interprets the leading `/` as the root of the current drive — `/c/...`
becomes `C:\c\Users\...` or `D:\c\Users\...` depending on which
drive the shell happens to be on, fails with ENOENT, and every
Edit/Write/MultiEdit blocks until the session restarts.
Convert absolute path args via `cygpath -w` (a Git Bash builtin) before
exec. Guarded by `command -v cygpath` so macOS/Linux fall straight
through unchanged; `cygpath -w` is idempotent on already-Windows paths
so the rare mixed-form case is safe. Only `/*` paths are converted —
Windows-form paths reaching the shim are already openable by python.exe.
Verified locally:
- cygpath absent on macOS → guard skips → POSIX behavior unchanged
- end-to-end shim invocation with a POSIX path on macOS exits 0
- stubbed cygpath -w on /c/Users/test/hook.py produces C:\Users\test\hook.py
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>