Skip to content

hook-guard read skips any path containing a directory with the same name as the last segment of GRAPHIFY_OUT #3959

Description

@chang258

Summary

When GRAPHIFY_OUT is a nested path whose last segment is a common name (e.g. artifacts/graphify), graphify hook-guard read silently skips the read nudge for every file under any directory with that name, not just the output directory. Example: tools/graphify/notes.md or .codex/skills/graphify/SKILL.md.

Version / environment

  • graphifyy 0.9.69
  • Windows 11, Claude Code PreToolUse hook (should be platform-independent)
  • GRAPHIFY_OUT=artifacts/graphify

Reproduction

From a project root containing artifacts/graphify/graph.json:

export GRAPHIFY_OUT=artifacts/graphify

# expected: no nudge (this is the output dir) -> OK
echo '{"tool_name":"Read","tool_input":{"file_path":"artifacts/graphify/GRAPH_REPORT.md"}}' | graphify hook-guard read

# expected: nudge (regular indexed source file) -> OK
echo '{"tool_name":"Read","tool_input":{"file_path":"src/app.py"}}' | graphify hook-guard read

# expected: nudge, actual: no output
echo '{"tool_name":"Read","tool_input":{"file_path":"tools/graphify/notes.md"}}' | graphify hook-guard read
echo '{"tool_name":"Read","tool_input":{"file_path":"tools/graphify/helper.py"}}' | graphify hook-guard read

The files under tools/graphify/ are in the graph (source_file present in graph.json), so they should be nudged like any other file.

Cause

In _run_hook_guard (cli.py), the read branch has this check:

under_out = "graphify-out/" in j or (GRAPHIFY_OUT_NAME.lower() + "/") in j

GRAPHIFY_OUT_NAME is os.path.basename(GRAPHIFY_OUT), which is graphify here. So the check becomes a substring test for graphify/ anywhere in the path. The "graphify-out/" literal has the same issue for any directory named graphify-out outside the project's real output dir, but that is less likely to happen in practice.

This looks like the same pattern as #2273 (detect.py, closed), but in the hook guard, which that fix did not cover.

Suggested fix

Compare against the resolved output directory instead of a bare name substring. Something like:

out_dir = out_path().resolve()
def _is_under_out(v: str) -> bool:
    try:
        p = Path(v)
        p = (root / p) if _is_cwd_relative(v) else p
        p.resolve().relative_to(out_dir)
        return True
    except (ValueError, OSError, RuntimeError):
        return False
under_out = any(_is_under_out(v) for v in (file_path, path) if v)

The Glob pattern field (e.g. artifacts/graphify/**) could keep a prefix match against the output dir's project-relative path.

Impact

Fails open: tools are never blocked, only the nudge is lost. In our repo this dropped the nudge for 11 indexed files (all of tools/graphify/), plus a skill directory. Because of this, we kept the old inline hook for Read|Glob and only switched Bash|Grep to hook-guard search, which works well. Thanks for #522 and #3121!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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