Skip to content

fix(hooks): strip shebang argument so pipx launchers resolve (#2629) - #2687

Open
ousamabenyounes wants to merge 3 commits into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2629
Open

fix(hooks): strip shebang argument so pipx launchers resolve (#2629)#2687
ousamabenyounes wants to merge 3 commits into
Graphify-Labs:v8from
ousamabenyounes:fix/issue-2629

Conversation

@ousamabenyounes

@ousamabenyounes ousamabenyounes commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #2629.

pipx launchers can put an interpreter argument in the shebang, for example #!/Users/me/.local/pipx/venvs/graphifyy/bin/python -E. The hook and skill interpreter probes were reading the whole shebang as if it were only a path. The trailing -E introduced a space, tripped the allowlist, and made the probes fall back to a bare python3 that often does not have graphify installed.

That produced the reporter's post-commit warning:

[graphify hook] could not locate a Python with graphify installed.

Fix

  • Strip only the leading #! marker and then strip shebang arguments before validating launcher interpreter paths.
  • Correctly parse /usr/bin/env -S ... and /usr/bin/env ... launchers.
  • Reject env-derived bare interpreter names like python before verification, avoiding PATH-controlled interpreter execution; explicit fallback probing still handles python3 / python.
  • Apply the same fix to the installed git hook probe, rendered POSIX skill probes, and the Aider/Devin monolith sources.
  • Tighten the monolith drift guard so this sanctioned shell change does not admit unrelated esac / fi drift.
  • Regenerate graphify/skill*.md and tools/skillgen/expected/* from the skillgen sources.
  • Keep the changelog entry under Unreleased.

Test verification (RED -> GREEN)

Original PR regression proof for #2629:

FAILED tests/test_hooks.py::test_hook_probe_resolves_pipx_shebang_with_argument
FAILED tests/test_hooks.py::test_generated_skill_probes_strip_shebang_argument
FAILED tests/test_hooks.py::test_interpreter_guard_rejects_truncated_shebang_path
3 failed, 1 passed

Additional RED proof for Graphify review advisories:

FAILED tests/test_hooks.py::test_interpreter_guard_resolves_env_split_shebang_argument
FAILED tests/test_hooks.py::test_interpreter_guard_does_not_corrupt_shebang_path_before_allowlist
2 failed
FAILED tests/test_hooks.py::test_interpreter_guard_rejects_path_controlled_env_split_interpreter
1 failed
FAILED tests/test_hooks.py::test_hook_probe_rejects_path_controlled_env_split_interpreter
1 failed

Focused GREEN validation on the fixed tree:

17 passed, 1 warning

Targeted commands run:

python3 -m pytest \
  tests/test_hooks.py::test_hook_probe_resolves_pipx_shebang_with_argument \
  tests/test_hooks.py::test_hook_probe_resolves_env_split_shebang_with_argument \
  tests/test_hooks.py::test_interpreter_guard_resolves_env_split_shebang_argument \
  -q --tb=short

uv run --frozen pytest \
  tests/test_hooks.py::test_hook_probe_resolves_pipx_shebang_with_argument \
  tests/test_hooks.py::test_hook_probe_resolves_env_split_shebang_with_argument \
  tests/test_hooks.py::test_hook_probe_rejects_path_controlled_env_split_interpreter \
  tests/test_hooks.py::test_shebang_allowlist_accepts_windows_backslash_path \
  tests/test_hooks.py::test_generated_skill_probes_strip_shebang_argument \
  tests/test_hooks.py::test_monolith_sources_strip_shebang_argument \
  tests/test_hooks.py::test_interpreter_guard_rejects_truncated_shebang_path \
  tests/test_hooks.py::test_interpreter_guard_resolves_env_split_shebang_argument \
  tests/test_hooks.py::test_interpreter_guard_rejects_path_controlled_env_split_interpreter \
  tests/test_hooks.py::test_interpreter_guard_does_not_corrupt_shebang_path_before_allowlist \
  tests/test_skillgen.py::test_monolith_shebang_argument_fix_sanctions_path_control_lines \
  tests/test_skillgen.py::test_monolith_roundtrip_passes_for_aider_and_devin \
  -q --tb=short

uv run --frozen python -m tools.skillgen --check
uv run --frozen python -m tools.skillgen --monolith-roundtrip
uv run --frozen ruff check graphify/hooks.py tests/test_hooks.py tests/test_skillgen.py tools/skillgen/gen.py
git diff --check
graphify update .

Coverage:

uv run --frozen pytest ... --cov=graphify.hooks --cov=tools.skillgen.gen
diff-coverage-gate: PASS no changed production lines with coverage data
python-ast-diff-coverage: PASS 5/5 changed executable statements covered

Note: an earlier ad-hoc line coverage helper (diff-coverage-python-coverable-3.log) was superseded because it treated shell lines inside Python string constants and docstring text as Python executable lines. The AST-aware gate above is the retained Python coverage check.

Final local full replay (./run-ci.sh) compared against an isolated origin/v8 baseline:

Baseline Python 3.10: 4 failed, 5295 passed, 11 skipped
Baseline Python 3.12: 4 failed, 5295 passed, 11 skipped
Final Python 3.10:    4 failed, 5310 passed, 11 skipped
Final Python 3.12:    4 failed, 5310 passed, 11 skipped

The four local failures are unchanged from baseline:

tests/test_extract.py::test_collect_files_skips_hidden
tests/test_ollama.py::test_detect_backend_ollama
tests/test_ollama.py::test_detect_backend_kimi_beats_ollama
tests/test_ollama.py::test_detect_backend_none_without_envvars

Install smoke and all skillgen validators pass (--check, --audit-coverage, --schema-singleton, --monolith-roundtrip, --always-on-roundtrip).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR fixes shebang parsing in the git post-commit hook and the interpreter-detection probes across the various agent skill files (graphify/hooks.py, skill-*.md, and their generated tools/skillgen expected fixtures). It adds a step that strips a trailing interpreter argument (e.g., the -E in pipx's #!/.../python -E) so the leading path word is kept before the character allowlist check. The change spans a CHANGELOG entry and the corresponding hook test file (tests/test_hooks.py), touching many parallel skill-template copies that share the same snippet.

No blocking issues surfaced. 3 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1133 functions depend on the 987 functions this change touches.

Health — grade B; 7 existing hotspot(s) in the area this change touches (pre-existing, not introduced here):

  • dispatch_command() — 2 callers, 117 callees (high)
  • install() — 25 callers, 6 callees (high)
  • dispatch_install_cli() — 2 callers, 31 callees (high)
  • uninstall() — 9 callers, 5 callees (high)
  • status() — 6 callers, 5 callees (medium)
  • uninstall_all() — 2 callers, 13 callees (high)
  • test_poisoned_manifest_is_healed() — 0 callers, 6 callees (medium)

Verification — 1133 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 994 function(s) in the blast radius were not formally verified this run

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

This PR fixes handling of Python interpreter shebangs in the git post-commit hook (graphify/hooks.py) and across the generated per-agent skill files. The change strips any trailing interpreter argument (e.g. pipx's #!/.../python -E) from the parsed shebang before it's checked against the path allowlist, so the valid interpreter path is retained rather than falling back to python3. The same logic is applied consistently across all the skill-*.md files, the skillgen tool fragments/expected outputs, and a CHANGELOG entry (#2629) is added. Tests in test_hooks.py are also touched, presumably to cover the new shebang-argument stripping behavior. Reviewers should check that the shell parameter-expansion edits are duplicated identically across every skill file and that the generated/expected skillgen fixtures stay in sync with the source templates.

No blocking issues surfaced. 4 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1166 functions depend on the 1017 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 117 callees
  • new: install() — 33 callers, 7 callees
  • new: dispatch_install_cli() — 2 callers, 31 callees
  • new: status() — 8 callers, 6 callees
  • new: uninstall() — 9 callers, 5 callees
  • new: uninstall_all() — 2 callers, 13 callees
  • new: test_poisoned_manifest_is_healed() — 0 callers, 6 callees

Verification — 1166 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1024 function(s) in the blast radius were not formally verified this run

· 7 more finding(s) on lines outside this diff (see the check run).

@ousamabenyounes

ousamabenyounes commented Aug 29, 2026

Copy link
Copy Markdown
Contributor Author

Updated this PR at ff8aa7a668435948a380802871fa239eceee8d60.

What changed since the previous version:

  • Rebased onto v8.
  • Extended the pipx shebang-argument fix to generated skill probes plus the Aider/Devin monolith sources.
  • Fixed the Devin subcommand interpreter guard so it strips shebang args and verifies the stripped candidate before writing graphify-out/.graphify_python.
  • Regenerated skill artifacts and moved the changelog entry to Unreleased.

Validation:

  • RED on origin/v8: copied-in tests fail for the hook probe, generated skill probe, and interpreter guard cases.
  • Extra RED: removing the Devin guard hunk makes test_monolith_sources_strip_shebang_argument fail.
  • Focused GREEN: 6 targeted tests pass.
  • Final local ./run-ci.sh: same four local baseline failures as clean origin/v8, with 5110 passes vs 5106 baseline.
  • GitHub checks on the pushed SHA are green: skillgen-check, security-scan, test (3.10), test (3.12).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.


Graphify review — findings

Fixes the git post-commit hook and skill interpreter probes to strip a shebang argument (#!/.../python -E from pipx) before treating it as a path, so the valid interpreter no longer trips the character allowlist and silently falls back to a python3 without graphify. The in-repo .graphify_python resolution now also verifies the chosen interpreter can import graphify and drops to python3 only when it can't, applied consistently across graphify/hooks.py and every generated skill file.

Worth a look

  • env -S shebang can be accepted as interpreter pathgraphify/hooks.py:88 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • env -S shebangs are parsed as the env option instead of the interpretergraphify/hooks.py:90 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • tr -d '#!' strips all '#' and '!' chars from shebang, corrupting env-based pathsgraphify/skill-copilot.md:78 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Valid env -S shebangs are parsed as the interpreter name '-S'graphify/skill-copilot.md:79 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Graphify python guard verifies PATH-controlled interpretertools/skillgen/fragments/shell/interpreter-guard-posix.md:12 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1580 functions depend on the 1352 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 122 callees
  • new: install() — 33 callers, 7 callees
  • new: render() — 13 callers, 5 callees
  • new: dispatch_install_cli() — 2 callers, 31 callees
  • new: status() — 8 callers, 6 callees
  • new: audit_coverage() — 8 callers, 6 callees
  • new: uninstall() — 9 callers, 5 callees
  • new: main() — 3 callers, 11 callees
  • …and 4 more — each is listed as a finding

Verification — 1580 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1424 function(s) in the blast radius were not formally verified this run

· 12 more finding(s) on lines outside this diff (see the check run).

…y-Labs#2629)

pipx writes the graphify launcher shebang with an argument
(`#!/.../python -E`). The interpreter probes used the whole shebang as a
path; the space tripped the character allowlist, the valid interpreter was
discarded, and the probe silently fell back to a `python3` without graphify
— printing "could not locate a Python with graphify installed" after every
commit and leaving the graph stale.

Strip any interpreter argument (and resolve `/usr/bin/env python`) before
using the shebang as a path, in the post-commit hook probe and both skill
interpreter probes (regenerated via skillgen).
Extend the pipx shebang-argument fix to aider/devin monolith sources and the Devin subcommand guard, verify stripped guard candidates before writing .graphify_python, and move the changelog entry to Unreleased.

RED->GREEN: red-devin-second-guard.log fails when the Devin guard fix is removed; green-review-blockers6.log passes 6 targeted tests. Full run-ci final5 matches baseline failure identities with 5110 passes vs 5106 baseline.
Handle /usr/bin/env -S shebang launchers, strip only the leading shebang marker, and reject env-derived bare interpreter names before verification. Regenerate skill artifacts and tighten the monolith drift guard coverage.
@ousamabenyounes

Copy link
Copy Markdown
Contributor Author

Updated this PR at d3b92f9d146b411f75b358d4ed0827accb5f15d8.

What changed since the previous version:

  • Rebased onto current v8.
  • Addressed the Graphify advisory items for /usr/bin/env -S parsing, destructive tr -d '#!' shebang stripping, and PATH-controlled env-derived interpreter names.
  • Kept Windows-style shebang paths accepted while rejecting bare env command names before verification.
  • Regenerated skill artifacts from tools/skillgen and tightened the monolith drift guard.

Validation:

  • RED: new env-S, shebang-corruption, and PATH-controlled-interpreter tests fail before the fix.
  • Focused GREEN: 17 passed.
  • python3 -m pytest reviewer repro subset: 3 passed.
  • tools.skillgen --check, tools.skillgen --monolith-roundtrip, ruff, git diff --check: pass.
  • Coverage: framework diff gate pass; AST-aware Python diff coverage 5/5 changed executable statements covered.
  • Final local ./run-ci.sh: same four local baseline failures as clean origin/v8, with 5310 passes vs 5295 baseline on both Python 3.10 and 3.12.
  • GitHub Actions on the pushed SHA are green: skillgen-check, security-scan, test (3.10), test (3.12).

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).


Graphify review — findings

Fixes the git post-commit hook and every skill interpreter probe to derive the Python path from a graphify shebang correctly: they strip a shebang argument (so pipx's #!/.../python -E resolves instead of tripping the path allowlist), parse /usr/bin/env -S ... launchers alongside plain /usr/bin/env, and reject bare PATH-controlled env names like python so an unverified interpreter can't be trusted. When the shebang yields no usable path, the skills fall back to python3 and verify import graphify before caching it in .graphify_python, ending the "could not locate a Python with graphify installed" message printed after every commit (#2629).

No blocking issues surfaced. 15 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 1674 functions depend on the 1503 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 123 callees
  • new: install() — 33 callers, 7 callees
  • new: render() — 13 callers, 5 callees
  • new: dispatch_install_cli() — 2 callers, 31 callees
  • new: status() — 8 callers, 6 callees
  • new: audit_coverage() — 8 callers, 6 callees
  • new: uninstall() — 9 callers, 5 callees
  • new: main() — 3 callers, 11 callees
  • …and 4 more — each is listed as a finding

Verification — 1674 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 1510 function(s) in the blast radius were not formally verified this run

· 12 more finding(s) on lines outside this diff (see the check run).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Interpreter probe fails on pipx installs: shebang with an argument is parsed as a file path

1 participant