Summary
The README (twice) and CHANGELOG 0.7.0 state that graphify hook install configures a git merge driver for graphify-out/graph.json. It doesn't. hooks.install() writes only the post-commit and post-checkout scripts — it never writes .gitattributes and never registers merge.graphify in .git/config.
The graphify merge-driver subcommand itself is fine. Only the registration half is missing, so the fix looks small.
Docs that promise it
README.md (team setup):
Run graphify hook install to auto-rebuild after each commit (AST only, no API cost). This also sets up a git merge driver so graph.json is never left with conflict markers — two devs committing in parallel get their graphs union-merged automatically.
README.md (troubleshooting):
graph.json has conflict markers after two devs commit at once
Run graphify hook install — it sets up a git merge driver that union-merges graph.json automatically so conflicts never happen.
CHANGELOG.md 0.7.0:
Feat: graphify hook install now also configures a git merge driver for graphify-out/graph.json … writes .gitattributes and registers graphify merge-driver in .git/config
cli.py help text also says the driver is "set up via hook install".
Repro (v0.9.15)
cd "$(mktemp -d)" && git init -q
graphify hook install
# post-commit: installed at .../.git/hooks/post-commit
# post-checkout: installed at .../.git/hooks/post-checkout
git config --get-regexp '^merge\.' # -> nothing
ls .gitattributes # -> No such file or directory
Expected: merge.graphify.driver registered and graphify-out/graph.json merge=graphify in .gitattributes.
Actual: neither. The two hook scripts are the only side effects.
Code path
cli.py elif cmd == "hook": (~L336) → hooks.install() (hooks.py L507–542), whose entire body resolves the hooks dir, pins sys.executable into both scripts, then:
commit_msg = _install_hook(hooks_dir, "post-commit", hook, _HOOK_MARKER)
checkout_msg = _install_hook(hooks_dir, "post-checkout", checkout, _CHECKOUT_MARKER)
return f"post-commit: {commit_msg}\npost-checkout: {checkout_msg}"
No merge-driver work anywhere in the function.
It doesn't appear to have regressed — it looks like it never shipped
Counting gitattributes|merge_driver|merge\.graphify|merge=graphify in each file at each tag:
| file |
v0.7.0 |
v0.8.0 |
v0.9.0 |
v0.9.15 |
graphify/hooks.py |
0 |
0 |
0 |
0 |
graphify/install.py |
0 |
— |
— |
0 |
graphify/cli.py |
0 |
— |
— |
0 |
graphify/__main__.py |
0 |
— |
— |
0 |
tests/test_hooks.py has 12 tests (test_install_creates_hook, test_install_idempotent, test_install_creates_post_checkout_hook, …) and zero reference merge. Nothing asserts the registration, which is consistent with the payload landing in 0.7.0 while the wiring didn't.
Impact
graphify-out/ is documented as something to commit, so this bites exactly the multi-dev workflow 0.7.0 was aimed at: two branches touching code both regenerate graph.json, and the merge leaves conflict markers in a large generated file nobody can hand-resolve. The troubleshooting entry for that symptom prescribes a command that doesn't fix it.
The driver works when wired by hand
Registering it manually and merging two branches that each add a distinct node union-merges cleanly, no conflict markers:
git config merge.graphify.name "graphify graph.json union merge"
git config merge.graphify.driver "$(command -v graphify) merge-driver %O %A %B"
printf 'graphify-out/graph.json merge=graphify\n' > .gitattributes
# branch A adds MarkWatched, branch B adds SyncQueue, then:
git merge feat-a
# -> nodes: ['Base', 'MarkWatched', 'SyncQueue']; no conflict markers
So merge-driver itself is good — hooks.install() just needs to register it.
Suggested fix
In hooks.install(), alongside the two _install_hook calls:
git config merge.graphify.name / merge.graphify.driver in the repo's .git/config.
- Ensure
graphify-out/graph.json merge=graphify in .gitattributes (append idempotently; it's a tracked file, so don't clobber).
One wrinkle worth considering: the driver command should probably be an absolute path, for the same reason the hook scripts already pin sys.executable. Git invokes the driver through a shell, and ~/.local/bin is routinely absent from PATH in GUI git clients and CI — the exact environments the pinning comment in install() calls out. A bare graphify merge-driver … would fail there and silently fall back to a conflicting merge.
If the intent is instead that users wire this themselves, the two README passages and the CHANGELOG entry need correcting — the troubleshooting one especially, since it's the answer given to someone already hitting the bug.
Found while setting this up on a fresh repo; happy to send a PR if the approach above looks right.
graphify 0.9.15 · macOS 15.5 (arm64) · Python 3.14 · uv tool install
Summary
The README (twice) and CHANGELOG 0.7.0 state that
graphify hook installconfigures a git merge driver forgraphify-out/graph.json. It doesn't.hooks.install()writes only thepost-commitandpost-checkoutscripts — it never writes.gitattributesand never registersmerge.graphifyin.git/config.The
graphify merge-driversubcommand itself is fine. Only the registration half is missing, so the fix looks small.Docs that promise it
README.md(team setup):README.md(troubleshooting):CHANGELOG.md0.7.0:cli.pyhelp text also says the driver is "set up via hook install".Repro (v0.9.15)
Expected:
merge.graphify.driverregistered andgraphify-out/graph.json merge=graphifyin.gitattributes.Actual: neither. The two hook scripts are the only side effects.
Code path
cli.pyelif cmd == "hook":(~L336) →hooks.install()(hooks.pyL507–542), whose entire body resolves the hooks dir, pinssys.executableinto both scripts, then:No merge-driver work anywhere in the function.
It doesn't appear to have regressed — it looks like it never shipped
Counting
gitattributes|merge_driver|merge\.graphify|merge=graphifyin each file at each tag:graphify/hooks.pygraphify/install.pygraphify/cli.pygraphify/__main__.pytests/test_hooks.pyhas 12 tests (test_install_creates_hook,test_install_idempotent,test_install_creates_post_checkout_hook, …) and zero referencemerge. Nothing asserts the registration, which is consistent with the payload landing in 0.7.0 while the wiring didn't.Impact
graphify-out/is documented as something to commit, so this bites exactly the multi-dev workflow 0.7.0 was aimed at: two branches touching code both regenerategraph.json, and the merge leaves conflict markers in a large generated file nobody can hand-resolve. The troubleshooting entry for that symptom prescribes a command that doesn't fix it.The driver works when wired by hand
Registering it manually and merging two branches that each add a distinct node union-merges cleanly, no conflict markers:
So
merge-driveritself is good —hooks.install()just needs to register it.Suggested fix
In
hooks.install(), alongside the two_install_hookcalls:git config merge.graphify.name/merge.graphify.driverin the repo's.git/config.graphify-out/graph.json merge=graphifyin.gitattributes(append idempotently; it's a tracked file, so don't clobber).One wrinkle worth considering: the driver command should probably be an absolute path, for the same reason the hook scripts already pin
sys.executable. Git invokes the driver through a shell, and~/.local/binis routinely absent from PATH in GUI git clients and CI — the exact environments the pinning comment ininstall()calls out. A baregraphify merge-driver …would fail there and silently fall back to a conflicting merge.If the intent is instead that users wire this themselves, the two README passages and the CHANGELOG entry need correcting — the troubleshooting one especially, since it's the answer given to someone already hitting the bug.
Found while setting this up on a fresh repo; happy to send a PR if the approach above looks right.
graphify 0.9.15 · macOS 15.5 (arm64) · Python 3.14 · uv tool install