Net: SKILL.md step 9 cleans up graphify-out/.needs_update (leading dot), but every writer and reader of that flag uses graphify-out/needs_update (no dot). So a full /graphify <path> rebuild never clears a flag set by --watch, and the read hook keeps emitting the stale nudge forever afterwards. One character.
The mismatch
Written and read without a dot:
graphify/watch.py:1560 — flag = Path(watch_path) / _GRAPHIFY_OUT / "needs_update"
graphify/watch.py:1569 — flag = watch_path / _GRAPHIFY_OUT / "needs_update"
graphify/watch.py:1288, 1333, 1534 — the # clear stale needs_update flag if present paths, all out / "needs_update"
graphify/cli.py:601 — if out_path("needs_update").exists(): stale = True
Deleted with a dot:
SKILL.md:612 — rm -f graphify-out/.needs_update 2>/dev/null || true
Why it matters
--watch writes needs_update when it sees a doc/paper/image change it cannot re-extract without an LLM (watch.py:1587). The read hook guard then treats the whole graph as stale for every source read (cli.py:599-604), which is correct and useful.
The intended escape hatch is a full rebuild through the skill, whose step 9 is supposed to clear the flag. Because it targets .needs_update, the real flag survives. Every subsequent Read gets _READ_NUDGE_STALE — "graph.json exists but may be STALE for this file ... run graphify update to refresh" — even immediately after a complete, successful rebuild that did re-extract those docs. graphify update does clear it (watch.py:1534), so the workaround is to run the CLI path, but the skill path is the documented one and it silently fails.
Reproduction: run --watch, touch a .md file so the flag appears, stop the watcher, then run the full /graphify . pipeline. graphify-out/needs_update is still on disk and the stale nudge persists.
Fix
-rm -f graphify-out/.needs_update 2>/dev/null || true
+rm -f graphify-out/needs_update 2>/dev/null || true
Found while auditing a v0.9.32 install (macOS, Claude Code, uv tool install graphifyy). Nothing else in the tree references the dotted name, so this looks like a typo rather than two intentional flags.
Net:
SKILL.mdstep 9 cleans upgraphify-out/.needs_update(leading dot), but every writer and reader of that flag usesgraphify-out/needs_update(no dot). So a full/graphify <path>rebuild never clears a flag set by--watch, and the read hook keeps emitting the stale nudge forever afterwards. One character.The mismatch
Written and read without a dot:
graphify/watch.py:1560—flag = Path(watch_path) / _GRAPHIFY_OUT / "needs_update"graphify/watch.py:1569—flag = watch_path / _GRAPHIFY_OUT / "needs_update"graphify/watch.py:1288,1333,1534— the# clear stale needs_update flag if presentpaths, allout / "needs_update"graphify/cli.py:601—if out_path("needs_update").exists(): stale = TrueDeleted with a dot:
SKILL.md:612—rm -f graphify-out/.needs_update 2>/dev/null || trueWhy it matters
--watchwritesneeds_updatewhen it sees a doc/paper/image change it cannot re-extract without an LLM (watch.py:1587). The read hook guard then treats the whole graph as stale for every source read (cli.py:599-604), which is correct and useful.The intended escape hatch is a full rebuild through the skill, whose step 9 is supposed to clear the flag. Because it targets
.needs_update, the real flag survives. Every subsequentReadgets_READ_NUDGE_STALE— "graph.json exists but may be STALE for this file ... rungraphify updateto refresh" — even immediately after a complete, successful rebuild that did re-extract those docs.graphify updatedoes clear it (watch.py:1534), so the workaround is to run the CLI path, but the skill path is the documented one and it silently fails.Reproduction: run
--watch, touch a.mdfile so the flag appears, stop the watcher, then run the full/graphify .pipeline.graphify-out/needs_updateis still on disk and the stale nudge persists.Fix
Found while auditing a v0.9.32 install (macOS, Claude Code,
uv tool install graphifyy). Nothing else in the tree references the dotted name, so this looks like a typo rather than two intentional flags.