Skip to content

post-commit hook silently exits 0 under uv tool install: never rebuilds, no error #2852

Description

@larrymandras

Summary

The graphify hook install post-commit hook cannot locate graphify when graphify was
installed via uv tool install graphifyy, and it fails silently (exit 0). The hook
appears installed, commits succeed normally, and nothing is ever rebuilt. Graphs go stale
indefinitely with no signal.

I found this on Windows after two repos' graphs had drifted 127h and 175h stale despite
the post-commit hook being installed in both. It turned out to affect 30 repos on this
machine, plus ~/.git-templates/hooks/post-commit, so every repo git init'd since the
template was written was born with a dead hook.

uv tool install is the install method the README recommends, so I suspect this is
widespread rather than specific to my setup.

Environment

  • Windows 11, Git for Windows (Git-Bash sh runs the hook)
  • graphifyy 0.9.46, installed via uv tool install --force "graphifyy[sql]"
  • Hook installed via graphify hook install

Two independent causes

1. The *.exe guard never fires on Git-Bash (cosmetic, already fixed in newer hooks)

GRAPHIFY_BIN=$(command -v graphify 2>/dev/null)
case "$GRAPHIFY_BIN" in
    *.exe) _SHEBANG="" ;;
    *)     _SHEBANG=$(head -1 "$GRAPHIFY_BIN" | sed 's/^#![[:space:]]*//') ;;
esac

Git-Bash's command -v graphify returns the launcher with no extension:

$ command -v graphify
/c/Users/USER/.local/bin/graphify

(shutil.which reports C:\Users\USER\.local\bin\graphify.EXE, but the shell strips it.)

So *.exe) never matches, and head -1 reads a PE binary. Visible symptom:

.git/hooks/post-commit: line 23: warning: command substitution: ignored null byte in input

A newer hook revision already fixes this by gating on a leading #! — one of my repos had
it. But that fix removes the only visible symptom while leaving cause 2 intact, which
makes the silent failure harder to find, not easier.

2. The interpreter fallback cannot see a uv-installed graphify (fatal)

if [ -z "$GRAPHIFY_PYTHON" ]; then
    if command -v python3 >/dev/null 2>&1 && python3 -c "import graphify" 2>/dev/null; then
        GRAPHIFY_PYTHON="python3"
    elif command -v python >/dev/null 2>&1 && python -c "import graphify" 2>/dev/null; then
        GRAPHIFY_PYTHON="python"
    else
        exit 0          # <-- silent death
    fi
fi

uv tool install places graphify in an isolated venv, so no system Python can import it:

$ python  -c "import graphify"   # ModuleNotFoundError
$ python3 -c "import graphify"   # ModuleNotFoundError
$ ~/AppData/Roaming/uv/tools/graphifyy/Scripts/python.exe -c "import graphify"   # OK

The hook therefore reaches else exit 0 on every commit.

Reproduce

uv tool install "graphifyy[sql]"
cd some-repo && graphify hook install
git commit --allow-empty -m test

Expected: [graphify hook] N file(s) changed - rebuilding graph...
Actual: no such line; hook exits 0; graph untouched.

Why it is hard to notice

exit 0 is indistinguishable from "no code files changed". There is no stderr, no
non-zero status, and the commit succeeds. The only tell is the absence of the
[graphify hook] ... line, which nobody watches for.

Relationship to existing issues

Suggested fix

Probe the uv tool venv before falling back to system Python:

if [ -z "$GRAPHIFY_PYTHON" ]; then
    for _cand in "$HOME/AppData/Roaming/uv/tools/graphifyy/Scripts/python.exe" \
                 "$HOME/.local/share/uv/tools/graphifyy/bin/python"; do
        if [ -x "$_cand" ] && "$_cand" -c "import graphify" 2>/dev/null; then
            GRAPHIFY_PYTHON="$_cand"
            break
        fi
    done
fi

Two further suggestions:

  1. Do not exit 0 silently. If no interpreter is found, print one line to stderr
    (graphify hook: no python with graphify importable; skipping rebuild) and still
    exit 0. A silent no-op is the reason this went unnoticed across 30 repos.
  2. graphify hook status could verify the hook can actually run, not just that the
    file exists — resolve the interpreter and report it.

Happy to open a PR if the approach looks right.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions