From 66a63f2c4385068b0776c0c17b5277c42ed56c9c Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Mon, 15 Jun 2026 12:57:33 -0700 Subject: [PATCH] =?UTF-8?q?security-guidance:=20probe=20for=20a=20non-PATH?= =?UTF-8?q?=203.10+=20interpreter=20on=20HOOK=5FPY=5FINCOMPATIBLE=20(2.0.6?= =?UTF-8?q?=20=E2=86=92=202.0.7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instrument-first for the macOS Python-3.9 cohort. v2.0.6 telemetry: ~13.6% of macOS sessions (~6,337 users) run on Apple's Python 3.9 → HOOK_PY_INCOMPATIBLE → the agentic reviewer can't load (needs 3.10+ syntax). That's ~12x macOS's build-failure rate and the single biggest macOS degradation. sg-python.sh only probes `python3.1x` on PATH, so these users have nothing newer ON PATH — but they may still have a 3.10+ installed at a standard location that isn't on the hook's PATH (Homebrew /opt/homebrew, python.org framework, etc.). Before building an explicit-path interpreter search, size the RECOVERABLE fraction: `_probe_alt_python()` checks Homebrew / python.org / distro locations for a 3.10+ binary and emits the highest found as `sdk_alt_py` (major*100+minor, or 0 = genuinely 3.9-only). Telemetry-only; probed ONLY on the HOOK_PY_INCOMPATIBLE path, so healthy sessions never run it. After a data cycle: non-zero sdk_alt_py = recoverable by an explicit-path search in sg-python.sh; 0 = needs a user-side Python install (the one-time notice is the only lever). That decides whether the search is worth building. Verified locally on macOS Python 3.13: - py_compile clean; probe returns 314 on this Mac (homebrew 3.14 present). - 7 new tests (test_altpython_probe.py): highest-version selection, 0-when-none (mocked os.access), framework/distro path parsing, only counts 3.10+, and emit gated on outcome==HOOK_PY_INCOMPATIBLE. - Full suite 575/575 + 2 skipped. No behavior change — purely additive telemetry on the incompatible path. Version 2.0.6 -> 2.0.7 per the per-PR-bump policy (#2114). Co-Authored-By: Claude Opus 4.7 (1M context) --- .claude-plugin/marketplace.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../hooks/ensure_agent_sdk.py | 48 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 2d5315e3..e132a973 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -2415,7 +2415,7 @@ { "name": "security-guidance", "description": "Security review for Claude-generated code. Pattern-based warnings on edits, LLM-powered diff review on Stop, and an agentic commit reviewer that catches injection, XSS, SSRF, hardcoded secrets, and 25+ other vulnerability classes.", - "version": "2.0.6", + "version": "2.0.7", "author": { "name": "Anthropic", "email": "support@anthropic.com" diff --git a/plugins/security-guidance/.claude-plugin/plugin.json b/plugins/security-guidance/.claude-plugin/plugin.json index f4cc218e..712db509 100644 --- a/plugins/security-guidance/.claude-plugin/plugin.json +++ b/plugins/security-guidance/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "security-guidance", - "version": "2.0.6", + "version": "2.0.7", "description": "Security review for Claude-generated code. Pattern-based warnings on edits, LLM-powered diff review on Stop, and an agentic commit reviewer that catches injection, XSS, SSRF, hardcoded secrets, and 25+ other vulnerability classes.", "author": { "name": "David Dworken", diff --git a/plugins/security-guidance/hooks/ensure_agent_sdk.py b/plugins/security-guidance/hooks/ensure_agent_sdk.py index 23312baa..f5d016db 100644 --- a/plugins/security-guidance/hooks/ensure_agent_sdk.py +++ b/plugins/security-guidance/hooks/ensure_agent_sdk.py @@ -318,6 +318,46 @@ def _probe_has_pip() -> bool: return False +def _probe_alt_python() -> int: + """When the hook interpreter is <3.10 (HOOK_PY_INCOMPATIBLE), look for a + 3.10+ interpreter at well-known install locations that aren't necessarily + on the hook's PATH — Homebrew (/opt/homebrew, /usr/local), python.org + framework builds, and the `py`/distro layouts. Returns the HIGHEST version + found encoded as major*100+minor (e.g. 312), or 0 if none. + + Purpose (telemetry only, for now): size how many of the macOS Python-3.9 + cohort actually HAVE a newer interpreter that sg-python.sh's PATH probe + missed — i.e. how many are RECOVERABLE by an explicit-path search vs. + genuinely 3.9-only. Emitted as sdk_alt_py. Existence-checks the versioned + binaries (cheap); a later explicit-path search would version-verify before + exec'ing. Probed only on the incompatible path, so healthy sessions never + pay for it.""" + candidates = [] + for minor in (14, 13, 12, 11, 10): + candidates += [ + f"/opt/homebrew/bin/python3.{minor}", # Apple-Silicon Homebrew + f"/usr/local/bin/python3.{minor}", # Intel Homebrew / python.org shim + f"/Library/Frameworks/Python.framework/Versions/3.{minor}/bin/python3", # python.org + f"/usr/bin/python3.{minor}", # distro-managed (Linux) + ] + best = 0 + for path in candidates: + try: + if os.access(path, os.X_OK): + # path name encodes the minor; parse it back to a code + base = os.path.basename(path) + minor = None + if base.startswith("python3."): + minor = int(base.split(".")[1]) + elif "/Versions/3." in path: + minor = int(path.split("/Versions/3.")[1].split("/")[0]) + if minor is not None: + best = max(best, 300 + minor) + except (OSError, ValueError, IndexError): + continue + return best + + def _pip_err_from_stderr(stderr_b): """Categorize a pip-install stderr into a known err_kind (the pip subset of SDK_BOOTSTRAP_ERR_CODES). Used by the --target fallback; mirrors the @@ -788,6 +828,14 @@ if __name__ == "__main__": # per healthy session. if _encode_err_kind(err_kind) == 11: metrics["sdk_has_pip"] = _probe_has_pip() + # When the hook interpreter is <3.10 (HOOK_PY_INCOMPATIBLE), probe for a + # 3.10+ interpreter at known non-PATH locations. Non-zero sdk_alt_py = + # this user is RECOVERABLE by an explicit-path search in sg-python.sh; 0 = + # genuinely 3.9-only (needs a user install). Sizes the macOS Py-3.9 cohort + # (~13.6% of macOS sessions) before we build the search. Incompatible path + # only — healthy sessions never run it. + if outcome == HOOK_PY_INCOMPATIBLE: + metrics["sdk_alt_py"] = _probe_alt_python() # Interpreter version (major*100 + minor, e.g. 309 / 312), emitted on # every bootstrap. Disambiguates the macOS cohort (Apple 3.9 vs a 3.10+ # with broken ensurepip) for both venv_ensurepip_fail AND