BOTH spellings of one file-tool event — [relative, absolute] — so a hook
test cannot pin only the spelling its author had in mind.
🔴 THIS EXISTS BECAUSE THE MISSING HALF HID A DEAD HOOK. Every hook test in
reach built { tool_input: { file_path: "drafts/papers/x.tex" } } by
hand — repo-relative, because that is how the hook's own prefixes are written.
Claude Code's Edit/Write/MultiEdit tools send an ABSOLUTE file_path,
PathView.under had no project root to reconcile the two, and so three
shipped react hooks matched nothing in a real session while their tests stayed
green. The tests were not weak; they reproduced the author's assumption
instead of the runtime's behaviour.
Iterating is the point — there is deliberately no singular builder here:
for (const e of fileToolEvents("drafts/papers/x.tex"))
assert.ok(hookFired(runHook(cmd, e)));
Each event also carries cwd, so a hook run WITHOUT $CLAUDE_PROJECT_DIR in
its environment still resolves a root — the same fallback the live runtime uses.
BOTH spellings of one file-tool event —
[relative, absolute]— so a hook test cannot pin only the spelling its author had in mind.🔴 THIS EXISTS BECAUSE THE MISSING HALF HID A DEAD HOOK. Every hook test in reach built
{ tool_input: { file_path: "drafts/papers/x.tex" } }by hand — repo-relative, because that is how the hook's own prefixes are written. Claude Code's Edit/Write/MultiEdit tools send an ABSOLUTEfile_path,PathView.underhad no project root to reconcile the two, and so three shipped react hooks matched nothing in a real session while their tests stayed green. The tests were not weak; they reproduced the author's assumption instead of the runtime's behaviour.Iterating is the point — there is deliberately no singular builder here:
Each event also carries
cwd, so a hook run WITHOUT$CLAUDE_PROJECT_DIRin its environment still resolves a root — the same fallback the live runtime uses.