diff --git a/CHANGELOG.md b/CHANGELOG.md index a73fed47d..b553094e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu - Fix: a PHP `use` import written with a leading-backslash / fully-qualified prefix now resolves to its target definition instead of being dropped (#2661, thanks @ousamabenyounes). - Fix: an unresolved local JS/TS import (to a file absent from the scan) now emits a stable, portable `ref` target id instead of leaking a per-checkout absolute-path slug (#2457, thanks @rohit-jsfreaky). - Fix: `graphify benchmark` no longer crashes on a node whose label is `None` (#2674, thanks @Arthuro0103). +- Fix: the git post-commit hook and skill interpreter probes now strip a shebang argument before using it as a path, so a pipx launcher (`#!/.../python -E`) resolves instead of silently falling back to a `python3` without graphify and printing "could not locate a Python with graphify installed" after every commit (#2629, thanks @hpstr5000). ## 0.9.40 (2026-08-11) diff --git a/graphify/hooks.py b/graphify/hooks.py index 5be58d12e..10a584b5b 100644 --- a/graphify/hooks.py +++ b/graphify/hooks.py @@ -76,6 +76,11 @@ */env\\ *) GRAPHIFY_PYTHON="${_SHEBANG#*/env }" ;; *) GRAPHIFY_PYTHON="$_SHEBANG" ;; esac + # Strip any interpreter argument: pipx writes `#!/.../python -E`, so the + # shebang carries a trailing argument that is not part of the path. Keep + # the leading word only, else the space trips the allowlist below and the + # valid interpreter is discarded (silent python3 fallback, #2629). + GRAPHIFY_PYTHON="${GRAPHIFY_PYTHON%% *}" # Allowlist: only keep characters valid in a filesystem path to prevent # injection if the shebang contains shell metacharacters. case "$GRAPHIFY_PYTHON" in diff --git a/graphify/skill-agents.md b/graphify/skill-agents.md index 190827d9a..e63ab8669 100644 --- a/graphify/skill-agents.md +++ b/graphify/skill-agents.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-amp.md b/graphify/skill-amp.md index 190827d9a..e63ab8669 100644 --- a/graphify/skill-amp.md +++ b/graphify/skill-amp.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-claw.md b/graphify/skill-claw.md index abd2811d2..a1f8a6a98 100644 --- a/graphify/skill-claw.md +++ b/graphify/skill-claw.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-codex.md b/graphify/skill-codex.md index af3f723c7..36b7542a8 100644 --- a/graphify/skill-codex.md +++ b/graphify/skill-codex.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-copilot.md b/graphify/skill-copilot.md index abd2811d2..a1f8a6a98 100644 --- a/graphify/skill-copilot.md +++ b/graphify/skill-copilot.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-droid.md b/graphify/skill-droid.md index fd148d485..83d82a9e8 100644 --- a/graphify/skill-droid.md +++ b/graphify/skill-droid.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-kilo.md b/graphify/skill-kilo.md index 3e70b050a..c5f6c3162 100644 --- a/graphify/skill-kilo.md +++ b/graphify/skill-kilo.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-kiro.md b/graphify/skill-kiro.md index abd2811d2..a1f8a6a98 100644 --- a/graphify/skill-kiro.md +++ b/graphify/skill-kiro.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-opencode.md b/graphify/skill-opencode.md index 91ced6067..63c223c24 100644 --- a/graphify/skill-opencode.md +++ b/graphify/skill-opencode.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -657,6 +662,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-pi.md b/graphify/skill-pi.md index abd2811d2..a1f8a6a98 100644 --- a/graphify/skill-pi.md +++ b/graphify/skill-pi.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-trae.md b/graphify/skill-trae.md index 050667bc2..b44ab56b4 100644 --- a/graphify/skill-trae.md +++ b/graphify/skill-trae.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -663,6 +668,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill-vscode.md b/graphify/skill-vscode.md index 20c7c0835..cf52abe55 100644 --- a/graphify/skill-vscode.md +++ b/graphify/skill-vscode.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -661,6 +666,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/graphify/skill.md b/graphify/skill.md index abd2811d2..a1f8a6a98 100644 --- a/graphify/skill.md +++ b/graphify/skill.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 9fffb32ba..190658f3f 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -976,3 +976,48 @@ def test_both_hooks_configured(tmp_path): for name in ("post-commit", "post-checkout"): hook_text = (repo / ".git" / "hooks" / name).read_text() assert 'export GRAPHIFY_VIZ_NODE_LIMIT="${GRAPHIFY_VIZ_NODE_LIMIT:-42}"' in hook_text + + +@pytest.mark.skipif(os.name == "nt", reason="POSIX shebang probe") +def test_hook_probe_resolves_pipx_shebang_with_argument(tmp_path): + """#2629: pipx writes `#!/.../python -E` — a shebang *with an argument*. + + The interpreter probe must strip that argument before using the shebang as a + path; otherwise the space trips the character allowlist, the valid pipx + interpreter is discarded, and the hook silently falls back to a `python3` + that has no graphify (printing 'could not locate a Python with graphify'). + """ + import sys + from graphify.hooks import _PYTHON_DETECT + + bindir = tmp_path / "bin" + bindir.mkdir() + launcher = bindir / "graphify" + # pipx-style launcher: shebang carries the `-E` argument. + launcher.write_text(f"#!{sys.executable} -E\nimport graphify\n", encoding="utf-8") + launcher.chmod(0o755) + + script = _PYTHON_DETECT + '\nprintf "%s" "$GRAPHIFY_PYTHON"\n' + env = dict(os.environ, PATH=f"{bindir}{os.pathsep}{os.environ['PATH']}") + resolved = subprocess.run( + ["bash", "-c", script], + cwd=tmp_path, + env=env, + capture_output=True, + text=True, + ).stdout.strip() + + assert resolved == sys.executable, ( + f"probe did not resolve the pipx interpreter from a shebang with an " + f"argument; got {resolved!r}, expected {sys.executable!r}" + ) + + +def test_generated_skill_probes_strip_shebang_argument(): + """#2629: both POSIX probe sites in the rendered skill (step-1 install and the + subcommand interpreter guard) must strip a shebang argument, so pipx + `#!/.../python -E` launchers resolve instead of silently reverting to python3.""" + skill = (Path(__file__).resolve().parent.parent / "graphify" / "skill.md").read_text() + # Both sites keep only the leading word of the shebang. + assert skill.count('_SHEBANG="${_SHEBANG%% *}"') >= 1, "step-1 probe does not strip shebang argument" + assert 'PYTHON="${PYTHON%% *}"' in skill, "interpreter guard does not strip shebang argument" diff --git a/tools/skillgen/expected/graphify__skill-agents.md b/tools/skillgen/expected/graphify__skill-agents.md index 190827d9a..e63ab8669 100644 --- a/tools/skillgen/expected/graphify__skill-agents.md +++ b/tools/skillgen/expected/graphify__skill-agents.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-amp.md b/tools/skillgen/expected/graphify__skill-amp.md index 190827d9a..e63ab8669 100644 --- a/tools/skillgen/expected/graphify__skill-amp.md +++ b/tools/skillgen/expected/graphify__skill-amp.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-claw.md b/tools/skillgen/expected/graphify__skill-claw.md index abd2811d2..a1f8a6a98 100644 --- a/tools/skillgen/expected/graphify__skill-claw.md +++ b/tools/skillgen/expected/graphify__skill-claw.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-codex.md b/tools/skillgen/expected/graphify__skill-codex.md index af3f723c7..36b7542a8 100644 --- a/tools/skillgen/expected/graphify__skill-codex.md +++ b/tools/skillgen/expected/graphify__skill-codex.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-copilot.md b/tools/skillgen/expected/graphify__skill-copilot.md index abd2811d2..a1f8a6a98 100644 --- a/tools/skillgen/expected/graphify__skill-copilot.md +++ b/tools/skillgen/expected/graphify__skill-copilot.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-droid.md b/tools/skillgen/expected/graphify__skill-droid.md index fd148d485..83d82a9e8 100644 --- a/tools/skillgen/expected/graphify__skill-droid.md +++ b/tools/skillgen/expected/graphify__skill-droid.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -662,6 +667,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-kilo.md b/tools/skillgen/expected/graphify__skill-kilo.md index 3e70b050a..c5f6c3162 100644 --- a/tools/skillgen/expected/graphify__skill-kilo.md +++ b/tools/skillgen/expected/graphify__skill-kilo.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-kiro.md b/tools/skillgen/expected/graphify__skill-kiro.md index abd2811d2..a1f8a6a98 100644 --- a/tools/skillgen/expected/graphify__skill-kiro.md +++ b/tools/skillgen/expected/graphify__skill-kiro.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-opencode.md b/tools/skillgen/expected/graphify__skill-opencode.md index 91ced6067..63c223c24 100644 --- a/tools/skillgen/expected/graphify__skill-opencode.md +++ b/tools/skillgen/expected/graphify__skill-opencode.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -657,6 +662,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-pi.md b/tools/skillgen/expected/graphify__skill-pi.md index abd2811d2..a1f8a6a98 100644 --- a/tools/skillgen/expected/graphify__skill-pi.md +++ b/tools/skillgen/expected/graphify__skill-pi.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-trae.md b/tools/skillgen/expected/graphify__skill-trae.md index 050667bc2..b44ab56b4 100644 --- a/tools/skillgen/expected/graphify__skill-trae.md +++ b/tools/skillgen/expected/graphify__skill-trae.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -663,6 +668,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill-vscode.md b/tools/skillgen/expected/graphify__skill-vscode.md index 20c7c0835..cf52abe55 100644 --- a/tools/skillgen/expected/graphify__skill-vscode.md +++ b/tools/skillgen/expected/graphify__skill-vscode.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -661,6 +666,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/expected/graphify__skill.md b/tools/skillgen/expected/graphify__skill.md index abd2811d2..a1f8a6a98 100644 --- a/tools/skillgen/expected/graphify__skill.md +++ b/tools/skillgen/expected/graphify__skill.md @@ -76,6 +76,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;; @@ -665,6 +670,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/fragments/shell/interpreter-guard-posix.md b/tools/skillgen/fragments/shell/interpreter-guard-posix.md index f7e344881..2b31e2bcc 100644 --- a/tools/skillgen/fragments/shell/interpreter-guard-posix.md +++ b/tools/skillgen/fragments/shell/interpreter-guard-posix.md @@ -3,6 +3,11 @@ if [ ! -f graphify-out/.graphify_python ]; then GRAPHIFY_BIN=$(which graphify 2>/dev/null) if [ -n "$GRAPHIFY_BIN" ]; then PYTHON=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` and strip any shebang argument (pipx + # writes `.../python -E`) before the allowlist check, else the space + # forces the unverified python3 fallback into .graphify_python (#2629). + case "$PYTHON" in */env\ *) PYTHON="${PYTHON#*/env }" ;; esac + PYTHON="${PYTHON%% *}" case "$PYTHON" in *[!a-zA-Z0-9/_.@-]*) PYTHON="python3" ;; esac else PYTHON="python3" diff --git a/tools/skillgen/fragments/shell/posix.md b/tools/skillgen/fragments/shell/posix.md index 3534417d2..978409a13 100644 --- a/tools/skillgen/fragments/shell/posix.md +++ b/tools/skillgen/fragments/shell/posix.md @@ -10,6 +10,11 @@ fi # 2. Read shebang from graphify binary (pipx and direct pip installs) if [ -z "$PYTHON" ] && [ -n "$GRAPHIFY_BIN" ]; then _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | tr -d '#!') + # Resolve `/usr/bin/env python` to the interpreter, then strip any argument + # (pipx writes `.../python -E`) so a shebang argument is not mistaken for the + # path and rejected by the allowlist below (silent python3 fallback, #2629). + case "$_SHEBANG" in */env\ *) _SHEBANG="${_SHEBANG#*/env }" ;; esac + _SHEBANG="${_SHEBANG%% *}" case "$_SHEBANG" in *[!a-zA-Z0-9/_.@-]*) ;; *) "$_SHEBANG" -c "import graphify" 2>/dev/null && PYTHON="$_SHEBANG" ;;