Repository navigation
bug: \codegraphcontext watch triggers re-indexing on filesystem events from paths matched by .cgcignore, causing stale/duplicate graph nodes #1275
Description
🐛 Bug Report
Environment
- CGC Version: v0.4.12 (latest PyPI)
- OS: Linux / macOS / Windows (reproducible on all platforms)
- Backend: KuzuDB (default), also reproducible with FalkorDB
Description
CodeGraphContext documents .cgcignore as the mechanism to exclude files and
directories from indexing (using .gitignore syntax). The codegraphcontext index
command correctly respects .cgcignore and skips matched paths during initial
indexing.
However, codegraphcontext watch — which uses watchdog to monitor the filesystem
for live changes and update the graph in real time — does NOT filter watchdog
filesystem events against the .cgcignore ruleset. This means:
- Any write to a file under an ignored path (e.g.
node_modules/,/build/,
*.log) fires a watchdogFileModifiedEventorFileCreatedEvent. - The event handler triggers a re-index of that file.
- The
.cgcignorecheck (if any) happens after the graph write attempt, not
before the watchdog listener is registered. - Result: ignored paths pollute the graph database with nodes for
node_modules/transitive dependencies, build artifacts, log files, etc.
Steps to Reproduce
# 1. Create a project with a .cgcignore
mkdir test_project && cd test_project
mkdir -p src node_modules/some-dep
echo "node_modules/" > .cgcignore
echo "def hello(): pass" > src/main.py
echo "def dep_func(): pass" > node_modules/some-dep/lib.py
# 2. Index and start watching
codegraphcontext index .
codegraphcontext watch .
# 3. In another terminal, write to an ignored path
echo "def new_dep(): pass" >> node_modules/some-dep/lib.py
# 4. Query the graph — the ignored file is now indexed
codegraphcontext analyze callers new_dep
# Expected: "No results (file is ignored)"
# Actual: "new_dep" is found as a graph node from node_modules/Root Cause
In src/codegraphcontext/, the file watcher (backed by watchdog) registers
an Observer on the target directory root without pre-filtering the observed
paths against the parsed .cgcignore ruleset (pathspec is a listed dependency
and is used during index, but its match_file() is not called in the event
handler that fires on FileModifiedEvent / FileCreatedEvent during watch).
This is analogous to Git itself — Git uses .gitignore to filter both
git add (index) and the working tree status checks. CGC currently only
filters the equivalent of git add but not the live event stream.
Expected Behavior
codegraphcontext watch should apply the same .cgcignore filtering logic to
every watchdog filesystem event before triggering any graph update. If a
changed file's path matches a .cgcignore pattern, the event should be silently
dropped — no re-indexing, no graph mutation, no log output for that file.
Proposed Fix
In the watchdog event handler class (likely in
src/codegraphcontext/watcher.py or similar):
import pathspec
class CGCEventHandler(FileSystemEventHandler):
def __init__(self, indexer, ignore_spec: pathspec.PathSpec, root: str):
self.indexer = indexer
self.ignore_spec = ignore_spec # Parsed .cgcignore
self.root = root
def _is_ignored(self, event_path: str) -> bool:
"""Return True if this path matches .cgcignore rules."""
rel_path = os.path.relpath(event_path, self.root)
return self.ignore_spec.match_file(rel_path)
def on_modified(self, event):
if event.is_directory or self._is_ignored(event.src_path):
return
self.indexer.reindex_file(event.src_path)
def on_created(self, event):
if event.is_directory or self._is_ignored(event.src_path):
return
self.indexer.index_file(event.src_path)
def on_deleted(self, event):
if event.is_directory or self._is_ignored(event.src_path):
return
self.indexer.remove_file_nodes(event.src_path)The ignore_spec should be loaded once at watcher startup:
cgcignore_path = os.path.join(target_dir, ".cgcignore")
if os.path.exists(cgcignore_path):
with open(cgcignore_path) as f:
ignore_spec = pathspec.PathSpec.from_lines("gitwildmatch", f)
else:
ignore_spec = pathspec.PathSpec.from_lines("gitwildmatch", [])Additional Improvements in Scope
.cgcignorereload on change: If.cgcignoreitself is modified while
watchis running, the watcher should reload the pathspec — currently it
would be stale for the rest of the session.- Configurable additional ignore patterns via CLI flag:
codegraphcontext watch . --ignore "*.test.py" --ignore "fixtures/"for
one-off exclusions without editing.cgcignore. - Add a regression test in
tests/that starts a watcher on a temp dir
with a.cgcignore, writes to an ignored file, and asserts no new graph
nodes were created.
Impact
This bug affects every user who relies on codegraphcontext watch for live
graph updates in projects with node_modules/, build/, dist/, or any
other large ignored directory. In a typical JavaScript/TypeScript project,
node_modules/ can contain 50,000+ files — a single npm install while
watching would queue 50,000 graph-write operations, degrading performance
severely and filling the graph with irrelevant dependency internals. This
also directly explains the performance degradation reported in issue #1076
("gets slower after 40%") for users who watch directories with large ignored
subtrees. The fix is contained to the watcher event handler and is
non-breaking for all existing usage.
I'd like to work on this issue — I'll fix the event handler filtering, add
the .cgcignore reload-on-change behavior, and write a regression test.
Could you assign this to me?
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
- StatusShow more project fieldsNo status
Activity
github-actions commentedon Jun 17, 2026
Hi! 👋 Join our CodeGraphContext Discord channel to collaborate: https://discord.gg/dR4QY32uYQ
prince-pokharna commentedon Jun 17, 2026
Hey @maintainer,
I have deeply gone through the project repo by local running n testing , and found this major bug after understanding and analysis of the code files and directory structure .
I would like to contribute to the issue based on my skills and requirement of the issue.
Please assign it to me.
Shashankss1205 commentedon Aug 1, 2026
Partial update from a source audit of
core/watcher.pyonmain(c0e0bed).The event path described here does filter now —
_is_supported_code_filecalls_should_ignorebefore any graph write, so a change under an ignored path is skipped. That part appears to have been addressed since v0.4.12.What was still broken is the initial scan and disk sync.
_iter_supported_filescalleddiscover_files_to_indexwithout forwarding thecgcignore_paththe handler was constructed with:So with
watch_directory(path, cgcignore_path="/custom/ignore")discovery fell back to the repo-local.cgcignoreonly. The initial scan indexed files the custom ignore excluded, while subsequent change events for those same files were correctly skipped — leaving exactly the stale nodes this issue describes, reached by a different route: the graph keeps entries the user asked to exclude, and they never update on edit.Fixed in #1508 by forwarding
cgcignore_pathand re-applying the handler's own spec to the discovered set.Worth confirming against your original reproduction before closing — if you were seeing pollution without a custom ignore file, that is a different path than the one I fixed and this issue should stay open.
Shashankss1205 commentedon Aug 1, 2026
Following up on my earlier comment — I've now run the full reproduction and both halves are covered, so closing this.
core/watcher.py:115gateson_created/on_modified(:349-355), with the guard at:253-254. This was fixed sometime after the v0.4.12 you reported against..cgcignoretoo — that was still broken until fix: correct 13 bugs across query validation, MCP gateway, indexing and CLI (v0.5.4) #1508 (HEAD6ad5edd), and was the remaining route by which ignored files reached the graph.So ignored paths no longer enter the graph via either path, and they no longer go stale on edit.
Please reopen if you still see pollution on 0.5.4 — particularly if you're driving the watcher through MCP rather than the CLI, since
tools/handlers/watcher_handlers.py:77still hard-codessync_on_start=True(noted on #1268), which exercises a slightly different startup path. Thanks for the thorough original report.