Skip to content

bug(indexer): Updating one file destroys globally-shared Module nodes and other files' edges #1392

Closed

Description

@Shashankss1205

tools/indexing/persistence/writer.py:1330-1338 (delete) vs :406 (global MERGE)

Module is one of three labels merged without a path, yet File -[:CONTAINS]-> Module is still created (item_mappings, :291). The per-file delete then runs:

MATCH (f:File {path: $path})
OPTIONAL MATCH (f)-[:CONTAINS]->(element)
DETACH DELETE f, element, p

which deletes the shared Module node — severing every other file's IMPORTS/INCLUDES edge to it. Re-adding the changed file recreates the Module node, but the other files are not reprocessed (they are neither CALLS callers nor INHERITS neighbours), so those edges are never restored.

Reproduced. Ruby repo where mod.rb defines module Shared and a.rb/b.rb include Shared:

[before] Module 'Shared' nodes = ['Shared']
         INCLUDES edges = [[email protected], [email protected]]
[after delete_file_from_graph('mod.rb')]     ← exactly what an edit triggers
         Module 'Shared' nodes = []
         INCLUDES edges = []

Fix. Exclude pathless shared labels from the cascade (WHERE element.path = $path), deleting only the CONTAINS relationship and letting _purge_dangling_pathless_nodes reclaim orphans.


Audit environment
Version codegraphcontext 0.5.2 (PyPI)
Commit df3af04
Backend FalkorDB Lite (falkordblite 0.9.0), Python 3.12.3, Linux
Corpus the CodeGraphContext repo itself — 784 files, 23 languages, 5,501 Function nodes

Audit ID I-H04 · severity high · from an end-to-end audit of CGC 0.5.2 (63 findings). Reported with the reproduction as run.

Activity

  1. added
    bugSomething isn't working
    severity: highWrong graph content, data loss, or a broken workflow with no clear signal
    area: indexerParsing, discovery, resolution, persistence
    on Jul 26, 2026
  2. github-actions commented on Jul 26, 2026

    @github-actions
    Contributor

    Hi! 👋 Join our CodeGraphContext Discord channel to collaborate: https://discord.gg/dR4QY32uYQ

  3. Shashankss1205 commented on Jul 26, 2026

    @Shashankss1205
    CollaboratorAuthor

    I tried to fix this and backed the fix out. Reporting what I found, because the issue is real but the suggested fix does not work.

    Confirmed, with a sharper reproduction

    The failure needs update_file_in_graph (what the watcher actually calls on an edit), not just delete_file_from_graph:

    repo: mod.rb defines `module Shared`; a.rb and b.rb both `include Shared`
    
    after index        : INCLUDES edges -> 2
    after edit mod.rb  : INCLUDES edges -> 0      ← never restored
    

    The Module node is recreated when mod.rb is re-added, but a.rb/b.rb are not reprocessed — they are neither CALLS callers nor INHERITS neighbours — so their INCLUDES edges are gone for good.

    Why the proposed fix does not work

    The report suggests filtering the cascade with WHERE element.path = $path. I implemented that and it changes nothing here, because the Ruby Module node does carry a path, equal to mod.rb:

    MATCH (f:File {path: $mod_rb})
    OPTIONAL MATCH (f)-[:CONTAINS]->(element)
    WHERE element.path = $mod_rb
    RETURN element.name, label(element)
    ->  helper  (Function)
    ->  Shared  (Module)      ← still matched, still deleted
    

    Also: the pathless case does not reproduce

    I tested the other half of the premise — a genuinely pathless shared Module — and it is not affected:

    a.py and b.py both `import os`   ->  Module 'os', 2 importers
    delete_file_from_graph(a.py)     ->  Module 'os' survives, 1 importer   ← correct
    

    That is because imported modules are reached by IMPORTS, not CONTAINS, and the cascade only follows CONTAINS. Both baseline main and the patched version behave identically here.

    What a real fix needs

    Exclude Module from the DETACH DELETE and drop only the CONTAINS relationship, so the node and its inbound INCLUDES/IMPORTS edges survive re-indexing of the defining file. The obstacle is that the label predicate is not portable — Kùzu uses label(n), Neo4j uses labels(n) — so it needs either a backend branch or a second query. The alternative is to re-link dependents after re-adding a file that defines shared symbols.

    I did not want to guess at that in a PR I could not verify on both backends. #1425 contains only the encoding fix; this one is untouched.

  4. moved this from Backlog tasks to In progress in CGC Progress Boardon Jul 26, 2026
  5. moved this from In progress to Done in CGC Progress Boardon Jul 31, 2026
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

    area: indexerParsing, discovery, resolution, persistencebugSomething isn't workingseverity: highWrong graph content, data loss, or a broken workflow with no clear signal

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions