fix(hooks): resolve the uv tool venv when pin and launcher probes miss (#2852) - #2929
Conversation
Graphify-Labs#2852) uv tool install puts graphify in an isolated venv no ambient python can import, and on Windows its launcher on PATH is a binary trampoline with no shebang, so with the pin dead every probe missed and the hook no-op'd with only a warning. Scan uv tool envs (UV_TOOL_DIR + default locations, POSIX bin/python and Windows Scripts/python.exe layouts) before the python3/python last resort, and gate the shebang read on a leading '#!' so launcher bytes never reach the parse.
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Adds a uv-tool-env probe to the hook interpreter-resolution chain in graphify/hooks.py: after the pin/shebang/ambient probes miss, it scans UV_TOOL_DIR and the default POSIX/Windows uv tool locations and adopts the first bin/python/Scripts/python.exe that passes the graphify import probe. Also gates the launcher shebang parse on a leading #!, so binary trampolines (uv installs on Windows reached without .exe) no longer feed bytes into the parse. Covers both with new test_hooks cases that run the emitted _PYTHON_DETECT under real sh against a simulated broken uv machine (#2852).
Worth a look
- UV tool scan executes arbitrary candidates from environment-controlled directory —
graphify/hooks.py:109· 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 — 343 functions depend on the 189 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 119 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 — 343 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: 196 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).
|
Re: the advisory on the uv tool-env scan executing candidates from an environment-controlled directory — the scan's trust scope matches the probes that already run in |
Problem
uv tool installis the README's recommended install method, but the post-commit hook cannot find that install when the install-time pin is dead, and then dies with no rebuild (#2852). On a machine where 30 repos plus~/.git-templatescarried the hook, graphs drifted 127h/175h stale with the hook "installed" and commits succeeding.Resolution chain under
uv tool installwith a dead_PINNED(git-template hooks, a pin rejected by the allowlist, or a venv relocated by an upgrade):.graphify_python— absent in repos that never built a graph.command -v graphify(Git-Bash) returns it without the.exesuffix, so the*.exeguard misses and there is no shebang to parse at all. On POSIX the shim's shebang does resolve, which is why this hides on Linux/macOS.python3/python— no ambient python can import graphify from an isolated uv venv → warning +exit 0, commit succeeds, nothing rebuilt.Two gaps in
graphify/hooks.py_PYTHON_DETECT(embedded in both the post-commit and post-checkout scripts):uv tool install: never rebuilds, no error #2852 cause 1);uv tool install: never rebuilds, no error #2852 cause 2), so the chain has no way back to a healthy venv install.Fix
python3/pythonlast resort: iterate${UV_TOOL_DIR:-},$HOME/.local/share/uv/tools,$HOME/AppData/Roaming/uv/toolsand probe*/bin/pythonand*/Scripts/python.exewith the existing_GFY_PROBE. A tool env is adopted only if its python passes the probe, so a co-installed tool without graphify never satisfies it (glob order is not trusted). This is the direction suggested in the issue, generalized to not hardcode the dist name.#!gate on the shebang read: the launcher's head bytes are only parsed as a shebang when the file actually starts with#!— a trampoline (or any non-script launcher) is rejected structurally instead of by the allowlist catching the parsed garbage.The loud
could not locate a Python with graphify installedstderr warning (restored onv8after 0.9.46's bareexit 0) is kept as the terminal failure — it stays out of this change's way and the scan makes it far rarer.Test
test_uv_tool_env_rescues_hook_when_pin_and_launcher_fail— post-commit hook silently exits 0 underuv tool install: never rebuilds, no error #2852's machine reproduced on POSIX (dead pin, no.graphify_python, binary trampoline launcher on PATH, ambient pythons that cannot import graphify): the emitted_PYTHON_DETECTrun under realshmust resolve the uv venv python, walking past a sibling tool env whose python lacks graphify. Fails on pre-fix code.test_uv_tool_env_honors_uv_tool_dir_and_windows_layout—UV_TOOL_DIRoverride + the WindowsScripts\python.exelayout. Fails pre-fix.test_shebang_parse_requires_leading_hash_bang— a non-script launcher whose first line merely names a working python must not hijack resolution (the misparse that used to feed trampoline bytes into the chain). Fails pre-fix.test_uv_tool_env_without_graphify_still_fails_loudly— the scan never adopts a venv without graphify; the chain still ends in the loud warning, never a silent adopt.tests/test_hooks.py96 passed;uv run --frozen pytest tests/ -qmatches the pre-PR baseline (thetest_skillgen.pyaudit-coverage andtest_ollama_retry_cap.pyfailures reproduce on a cleanorigin/v8tree in this checkout and are untouched by this diff; CI'sfetch-depth: 0covers the former).Sandbox end-to-end against a real
uv tool installof the local tree: with the pin blanked and the launcher replaced by a binary blob, pre-fix commit → warning, log untouched; post-fix commit → hook resolves~/.local/share/uv/tools/graphifyy/bin/pythonand the detached rebuild runs ([graphify hook] 1 file(s) changed - rebuilding graph...).Related: #2629 (pipx
python -Eshebang arg) is the same probe chain but a different parse failure and is not addressed here. Builds on the #2126/#2166 allowlist lineage in this block.