Summary
The read hook's out-of-project guard (#1840) treats a rooted path with no drive letter as cwd-relative, which it is not on Windows. The result is that the read nudge — and in strict mode the once-per-session deny — fires for files that live outside the project entirely.
graphify/cli.py, in _run_hook_guard:
for v in explicit:
p = Path(v)
if not p.is_absolute():
in_project = True # relative -> anchored at cwd == in project
break
That comment is the bug. On Windows a path like /somewhere/else/x.py is not absolute — it carries no drive — but it is not anchored at cwd either. Windows anchors it at the current drive root:
>>> os.getcwd()
'C:\\graphify\\graphify'
>>> Path('/somewhere/else/x.py').is_absolute()
False
>>> Path('/somewhere/else/x.py').resolve()
WindowsPath('C:/somewhere/else/x.py') # not under cwd
So the guard short-circuits to in_project = True and never reaches the relative_to(root) containment check two lines below that would have rejected it.
C:x.py is the same trap from the other side: drive-relative, anchored at that drive's current directory rather than ours.
Impact
/-rooted paths are the normal form for hosts that speak POSIX paths, and for agents driven from WSL or Git Bash on a Windows machine. When one of those sends a file_path outside the project:
- soft mode injects the
MANDATORY: ... You MUST run graphify before reading source files context for a file the graph has never indexed;
- strict mode can spend the session's single deny on it, so the one block the guard is allowed lands on an unrelated file.
The whole point of #1840 was that out-of-project reads are ignored, so this is the guard failing at its stated job rather than a cosmetic issue.
Reproduce
This is already covered by an existing test — tests/test_hook_strict.py::test_out_of_project_read_silenced has simply been failing on Windows:
$ uv run --frozen pytest tests/test_hook_strict.py::test_out_of_project_read_silenced
E assert '{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"MANDATORY: ...' == ''
tests\test_hook_strict.py:124: AssertionError
The payload it sends is {"tool_input": {"file_path": "/somewhere/else/x.py"}}, and the assertion is that the guard stays silent.
Why CI never caught it
.github/workflows/ci.yml runs the test job on ubuntu-latest only. On POSIX /somewhere/else/x.py is absolute, so the guard takes the containment path and behaves correctly — the bug is unreachable there.
Suggested fix
The question the guard actually asks is "is this path resolved against cwd?", and the answer to that is "no root and no drive", not "not absolute":
pure = PureWindowsPath(value) if os.name == "nt" else PurePosixPath(value)
return not pure.root and not pure.drive
These stay the host's own rules. paths.is_absolute_any_platform is deliberately not the right tool here — its docstring says so explicitly ("Code resolving a path against the real local filesystem (cli, detect, hooks) must keep using Path.is_absolute()"), and that guidance is correct; the fix is that "absolute" was the wrong local predicate, not that the path should be judged by both platforms' rules.
On POSIX root is set exactly when a path is absolute and drive is always empty, so this is a no-op there.
I have this working with tests and will open a PR shortly.
Environment
|
|
| graphify |
v8 @ 4fca621 (0.9.44) |
| Python |
3.12, Windows 11 |
Summary
The read hook's out-of-project guard (#1840) treats a rooted path with no drive letter as cwd-relative, which it is not on Windows. The result is that the read nudge — and in strict mode the once-per-session deny — fires for files that live outside the project entirely.
graphify/cli.py, in_run_hook_guard:That comment is the bug. On Windows a path like
/somewhere/else/x.pyis not absolute — it carries no drive — but it is not anchored at cwd either. Windows anchors it at the current drive root:So the guard short-circuits to
in_project = Trueand never reaches therelative_to(root)containment check two lines below that would have rejected it.C:x.pyis the same trap from the other side: drive-relative, anchored at that drive's current directory rather than ours.Impact
/-rooted paths are the normal form for hosts that speak POSIX paths, and for agents driven from WSL or Git Bash on a Windows machine. When one of those sends afile_pathoutside the project:MANDATORY: ... You MUST run graphify before reading source filescontext for a file the graph has never indexed;The whole point of #1840 was that out-of-project reads are ignored, so this is the guard failing at its stated job rather than a cosmetic issue.
Reproduce
This is already covered by an existing test —
tests/test_hook_strict.py::test_out_of_project_read_silencedhas simply been failing on Windows:The payload it sends is
{"tool_input": {"file_path": "/somewhere/else/x.py"}}, and the assertion is that the guard stays silent.Why CI never caught it
.github/workflows/ci.ymlruns the test job onubuntu-latestonly. On POSIX/somewhere/else/x.pyis absolute, so the guard takes the containment path and behaves correctly — the bug is unreachable there.Suggested fix
The question the guard actually asks is "is this path resolved against cwd?", and the answer to that is "no root and no drive", not "not absolute":
These stay the host's own rules.
paths.is_absolute_any_platformis deliberately not the right tool here — its docstring says so explicitly ("Code resolving a path against the real local filesystem (cli,detect,hooks) must keep usingPath.is_absolute()"), and that guidance is correct; the fix is that "absolute" was the wrong local predicate, not that the path should be judged by both platforms' rules.On POSIX
rootis set exactly when a path is absolute anddriveis always empty, so this is a no-op there.I have this working with tests and will open a PR shortly.
Environment
v8@4fca621(0.9.44)