Skip to content

--exclude patterns only apply to the initial scan — update/watch/hook rebuilds silently re-include excluded paths #1886

Description

@Alwyn93

Summary

CLI --exclude patterns are applied only by the initial scan. Every other re-detection path — graphify update, the watcher, and the git-hook rebuilds — re-runs detect() without them, so deliberately excluded paths silently re-enter the graph on the first rebuild.

Verified on v8 HEAD (961b78e).

Repro

$ mkdir -p repro/vendor && cd repro
$ printf 'def foo():\n    return 1\n' > main.py
$ printf 'def bar():\n    return 2\n' > vendor/big.py

$ graphify extract . --exclude vendor --no-cluster
[graphify extract] found 1 code, 0 docs, 0 papers, 0 images
[graphify extract] wrote .../graph.json — 2 nodes, 1 edges (no clustering)
# graph source_files: ['main.py']  ✓ exclude honored

$ graphify update .
[graphify watch] Rebuilt: 4 nodes, 2 edges, 2 communities
# graph source_files: ['main.py', 'vendor/big.py']  ✗ vendor is back, silently

There is also no way to re-state the excludes at update time — update rejects the flag:

$ graphify update . --exclude vendor
error: unknown update option: --exclude

Root cause

  • --exclude is parsed into cli_excludes and forwarded as extra_excludes only in the extract command path (cli.py:2029-2032, cli.py:2100, cli.py:2112); detect() appends these root-anchored patterns last (detect.py:1163-1166).
  • extra_excludes is never persisted. The build saves the scan root (.graphify_root, watch.py:967) — which is what makes update feel like a faithful re-run of the original build — but not the exclude patterns.
  • _rebuild_code() (watch.py:721) has no extra_excludes parameter at all; its detect call is detect(watch_path, follow_symlinks=follow_symlinks) (watch.py:822).
  • All four rebuild drivers therefore scan unfiltered: update (cli.py:1275), the watch loop (watch.py:1251), the post-commit hook (hooks.py:134), and the post-checkout hook (hooks.py:175).

Suggested fix

Persist the build's extra_excludes alongside .graphify_root (or in manifest.json) and have _rebuild_code() re-apply them; optionally also accept --exclude on update as an explicit override. This is the same shape as #1800 (--exclude-hubs not surviving hook/watch rebuilds) — a single persisted "build options" sidecar would solve both.

Workaround (verified)

Put the patterns in a .graphifyignore at the scan root instead of using --exclude — ignore files are re-read from disk on every detect(), so they survive update/watch/hooks. One wrinkle: converting an already-polluted graph back needs graphify update --force, since the shrinking node count otherwise trips the safety check in to_json.

Context

Found while diagnosing #1873 (fix in #1885): with the nested-ignore bug live, a CLI --exclude .hypothesis workaround appeared to fix a zeroed scan, then regressed on the next update — which is how this gap surfaced.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions