# How Incremental Parsing Handles Cross-Language Symbol Collisions in Code-Graph-RAG

> Learn how Code-Graph-RAG prevents cross-language symbol collisions with incremental parsing. Discover how it disambiguates names and maintains a QN to file path map for accurate indexing.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: internals
- Published: 2026-08-13

---

**The Code-Graph-RAG indexer prevents cross-language symbol collisions during incremental parsing by maintaining a persistent module qualified name (QN) to file path map, rehydrating it from the existing graph before each run, and automatically disambiguating conflicting basenames by appending file extensions.**

When indexing polyglot codebases containing files like [`shapes.rs`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.rs) and [`shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.cpp), the **cgr** tool must ensure that symbols from different languages remain distinct even when only a subset of files is reparsed. The solution centers on the `_disambiguate_module_qn` function in [`codebase_rag/parsers/definition_processor.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/definition_processor.py), which leverages a rehydrated `module_qn_to_file_path` dictionary to preserve symbol uniqueness across incremental updates.

## The Challenge of Cross-Language Name Collisions

During a **full build**, the indexer processes every source file simultaneously, allowing the disambiguation routine to see all files sharing the same basename. The system can immediately detect that [`shapes.rs`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.rs) and [`shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.cpp) both map to the module qualified name `proj.shapes` and resolve the conflict accordingly.

However, **incremental runs** present a unique problem. The `GraphUpdater.run` method (around line 1265 in [`codebase_rag/graph_updater.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/graph_updater.py)) only reparses files whose hashes have changed. If a new [`shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.cpp) is added while [`shapes.rs`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.rs) remains untouched, the incremental parser would see only the new file and might incorrectly assign it the bare module QN `proj.shapes`, potentially overwriting the existing Rust module node.

## How the Disambiguation Mechanism Works

### The `_disambiguate_module_qn` Function

The core logic resides in [`definition_processor.py`](https://github.com/vitali87/code-graph-rag/blob/main/definition_processor.py) (lines 84–95). When processing a file, the routine first checks if the target module QN already exists in `self.module_qn_to_file_path`, a dictionary that maps qualified names to their canonical file paths:

```python
def _disambiguate_module_qn(self, module_qn: str, file_path: Path) -> str:
    # Two files that share a basename but differ by extension (foo.py / foo.cpp)

    # strip to the same module qn. Append the extension to the later one so their

    # module nodes and all derived class/method qns stay distinct instead of

    # colliding under the qualified_name constraint.

    existing = self.module_qn_to_file_path.get(module_qn)
    if existing is None or existing == file_path:
        return module_qn
    return (
        f"{module_qn}{cs.SEPARATOR_DOT}{file_path.suffix.lstrip(cs.SEPARATOR_DOT)}"
    )

```

If the QN is already owned by a different file, the function appends the new file’s extension (e.g., transforming `proj.shapes` into [`proj.shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/proj.shapes.cpp)), ensuring that derived symbols like classes and methods inherit unique qualified names.

### Persistent Module Map Rehydration

To prevent newly added files from "stealing" bare QNs from unchanged siblings, the incremental pipeline rehydrates the `module_qn_to_file_path` map from the existing graph before re-parsing begins. The `GraphUpdater` clears in-memory entries only for files that are actually being reparsed; all other entries persist, allowing `_disambiguate_module_qn` to detect collisions against files processed in previous runs.

## Practical Example: Adding a C++ Sibling to a Rust Module

The test suite in [`codebase_rag/tests/test_incremental_cross_language_collision.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tests/test_incremental_cross_language_collision.py) validates this behavior (lines 66–95). Consider a scenario where a Rust file is indexed first, followed by the addition of a C++ counterpart:

```python

# Example: Incremental addition of a C++ sibling next to an existing Rust module

temp_repo = Path("/tmp/project")
(temp_repo / "shapes.rs").write_text(_RS)         # Rust file indexed first

store = _StatefulIngestor()
_index(store, temp_repo, force=False)            # Full index → proj.shapes

# Add C++ sibling and force reparse

(temp_repo / "shapes.cpp").write_text(_CPP)
os.utime(temp_repo / "shapes.cpp", (future, future))

_index(store, temp_repo, force=False)            # Incremental run

modules = _shapes_modules(store)
assert modules["shapes.rs"] == "proj.shapes"
assert modules["shapes.cpp"].endswith(".cpp")    # Disambiguated QN

```

In this workflow, the first index assigns `proj.shapes` to the Rust file. During the incremental update, the rehydrated map contains the existing entry, so the C++ file receives [`proj.shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/proj.shapes.cpp) instead of colliding with the established node.

## Handling File Deletion and Replacement

The same test file (lines 101–126) verifies the **delete-then-add** scenario. When the original file is removed from the repository and a new file with the same basename but different extension is added, the new file correctly receives the bare module QN. This behavior mirrors a clean build, ensuring consistent symbol naming even as the codebase evolves.

## Summary

- **Persistent mapping**: The `module_qn_to_file_path` dictionary is rehydrated from the existing graph before incremental runs, preserving knowledge of unchanged files.
- **Extension-based disambiguation**: The `_disambiguate_module_qn` function in [`definition_processor.py`](https://github.com/vitali87/code-graph-rag/blob/main/definition_processor.py) detects collisions and appends file suffixes to create unique qualified names.
- **Incremental safety**: By only clearing entries for reparsed files, the system prevents newly added siblings from overwriting established symbols in other languages.
- **Validated behavior**: The [`test_incremental_cross_language_collision.py`](https://github.com/vitali87/code-graph-rag/blob/main/test_incremental_cross_language_collision.py) suite confirms that both [`shapes.rs`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.rs) and [`shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/shapes.cpp) coexist as `proj.shapes` and [`proj.shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/proj.shapes.cpp) respectively.

## Frequently Asked Questions

### What causes cross-language symbol collisions in incremental parsing?

Cross-language collisions occur when two source files share the same basename but use different extensions (e.g., [`utils.py`](https://github.com/vitali87/code-graph-rag/blob/main/utils.py) and [`utils.rs`](https://github.com/vitali87/code-graph-rag/blob/main/utils.rs)). During a full index, the parser sees both files and can disambiguate them. During incremental parsing, however, the parser might only see the new file, risking an overwrite of the existing module node if the system does not maintain state from previous runs.

### How does the Code-Graph-RAG indexer maintain symbol uniqueness across languages?

According to the source code in [`codebase_rag/parsers/definition_processor.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/parsers/definition_processor.py), the indexer maintains a `module_qn_to_file_path` mapping that tracks which file owns each module qualified name. When `_disambiguate_module_qn` detects that a desired QN is already assigned to a different file path, it appends the file extension to the new file's module name, creating distinct nodes like `proj.shapes` and [`proj.shapes.cpp`](https://github.com/vitali87/code-graph-rag/blob/main/proj.shapes.cpp).

### What happens when a file is deleted and replaced with a different language version?

When the original file is deleted and a new file with the same basename is added, the incremental parser updates the `module_qn_to_file_path` map to reflect the deletion. The new file then claims the bare module QN (e.g., `proj.shapes`), matching the behavior of a clean build. The test suite confirms this behavior in [`test_incremental_cross_language_collision.py`](https://github.com/vitali87/code-graph-rag/blob/main/test_incremental_cross_language_collision.py) (lines 101–126).

### Where is the incremental parsing logic orchestrated in the codebase?

The high-level orchestration occurs in [`codebase_rag/graph_updater.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/graph_updater.py) around line 1265, where the `GraphUpdater.run` method manages the incremental update cycle. This component determines which files require reparsing and ensures that the module QN mapping is cleared only for those specific files, preserving the rest of the graph state for collision detection.