Code-Graph-RAG Limitations: 9 Critical Constraints in the Multi-Language Analysis Tool
Code-Graph-RAG (cgr) has nine major limitations spanning language coverage gaps, static analysis boundaries, Tree-sitter grammar quirks, and incremental indexing edge cases that constrain its retrieval accuracy and completeness.
Code-Graph-RAG is an open-source knowledge graph builder that converts multi-language repositories into queryable Memgraph structures using Tree-sitter parsers. While it supports 14 programming languages without requiring compilation, its architecture introduces specific technical constraints that users must understand when evaluating retrieval results. This guide examines each limitation with direct references to the vitali87/code-graph-rag source code and evaluation framework.
Language Coverage and Maturity Gaps
Partial Language Support for Scala and Others
Code-Graph-RAG officially supports Python, TypeScript/TSX, JavaScript, Rust, Go, Java, C, C++, C#, PHP, Lua, Dart, Scala, and Ruby. However, not all languages reach the same maturity level.
According to the documented language support matrix, Scala remains in active development and some languages rely on a pluggable ast-grep tier that emits only a limited set of node types. This creates uneven graph fidelity across the supported language set.
# Enable typed-language mode for better recall in supported languages
cgr start --repo-path ./rust-demo --languages rust --typed
The --typed flag improves type inference where implemented, but falls back to simplified parsing for languages with limited native support.
Name-Based Retrieval Oracle Constraints
Simple Name Matching Creates Recall Gaps
The evaluation harness in evals/README.md uses a name-based oracle that matches symbols solely by their simple identifier names. This design choice introduces deliberate boundaries on what cgr considers correct.
The oracle does not count several patterns:
- Anonymous arrow functions without explicit names
- Built-in name collisions across namespaces
- Constructor resolution when
__init__is implicit
Multiple symbols sharing a common name (e.g., process_data in different modules) create ambiguity that the oracle cannot disambiguate, leading to defensible but incomplete recall measurements.
Tree-Sitter Grammar Quirks and Pre-Processor Blindness
C++ Grammar Drops Edges in Conditional Blocks
Tree-sitter's C++ grammar contains documented bugs affecting graph construction. Specifically, the parser drops edges inside #if/#endif preprocessor blocks, and the C grammar does not evaluate pre-processor conditionals at all.
This produces false positives and negatives in the resulting graph:
#ifdef DEBUG
void debug_log(const char* msg); // Always appears in graph, even if DEBUG undefined
#endif
void production_code() {
debug_log("test"); // Edge created even when DEBUG is undefined
}
The static analyzer cannot determine which conditional compilation branches are active without executing the pre-processor.
Dead Code Persists in Graph
Because cgr's Tree-sitter front-end does not execute the C/C++ pre-processor, code conditionally compiled out still generates nodes and edges. These represent dead paths in the compiled binary but live paths in the knowledge graph.
The evaluation documentation notes this as an accepted trade-off for language-agnostic operation without requiring a full compiler toolchain.
Dynamic and Runtime-Only Construct Blindness
Static Analysis Cannot Capture Runtime Resolution
Code-Graph-RAG's graph construction is purely static. It cannot trace calls resolved through:
- Python's
getattr()orsetattr() - JavaScript's dynamic property access
- Reflection APIs in Java, Go, or C#
- Dynamically generated or
eval'd code
The evaluation framework explicitly measures this as a recall ceiling rather than a bug. Cgr intentionally omits these edges because they require runtime execution or sophisticated symbolic execution beyond the project's scope.
Type Inference Gaps in Multiple Languages
Limited to Local Variable Types
For Rust, Go, JavaScript, and Lua, cgr performs type inference only on local variable types within function scope. It does not implement full inter-procedural type inference across call boundaries.
This limitation manifests as missing CALLS edges when method receivers require type propagation through parameter passing or return values:
// Type inferred locally: OK
let processor: DataProcessor = DataProcessor::new();
processor.process(); // CALLS edge created
// Type from function return: may miss edge
let processor = create_processor(); // Return type not always propagated
processor.process(); // CALLS edge may be omitted
Per-language type inference implementations reside in parsers/<language>/type_inference.py. The Rust and Go modules specifically document this local-only scope limitation.
Incremental Re-Indexing Edge Loss
Inbound Edge Restoration Imperfections
When using incremental updates instead of full re-indexing, cgr updates only changed files. This creates edge synchronization challenges:
# Incremental update after editing a single file
cgr start --repo-path ./my_repo --incremental
During incremental processing:
- Inbound edges from unchanged callers to modified functions must be restored
- Outbound edges from modified functions may not fully re-resolve
- Deep type-inference chains across unchanged→changed boundaries diverge
The current implementation restores most inbound edges but retains a small tail of divergence for complex type-inference scenarios involving multiple file dependencies.
Cross-Project Resolution Limitations
Synthetic Fixtures vs. Real Monorepos
Code-Graph-RAG can resolve imports across top-level packages, but the evaluation harness validates this capability using synthetic multi-package fixtures rather than production-scale monorepos.
Real-world monorepos may expose edge-case bugs in:
- Circular dependency handling
- Version-conflict resolution across packages
- Namespace collision in merged graph schemas
The cross-project evaluation description in evals/README.md lines 30-33 explicitly notes this coverage gap between demonstrated capability and battle-tested reliability.
Performance vs. Fidelity Trade-Offs
Compiler Front-End Alternative Reduces Recall
Cgr defaults to Tree-sitter parsing for speed. An alternative using full compiler front-ends (e.g., libclang for C++) improves edge fidelity but reduces recall dramatically:
| Approach | Speed | Edge Fidelity | Total Recall |
|---|---|---|---|
| Tree-sitter (default) | Fast | Moderate | Higher |
| libclang front-end | Slower | Higher | Lower |
The compiler front-end emits fewer total edges due to stricter semantic analysis, creating a tension between precision and coverage that users must navigate based on their retrieval requirements.
Summary
- Language coverage spans 14 languages with uneven maturity; Scala and ast-grep-tier languages have reduced node type support
- Name-based oracle in evaluation deliberately excludes anonymous functions and certain collision patterns, bounding measurable recall
- Tree-sitter grammar quirks in C/C++ drop edges in pre-processor blocks without evaluating conditionals
- Pre-processor blindness causes dead code to appear as live graph edges
- Dynamic constructs (
getattr, reflection,eval) are inherently invisible to static analysis - Type inference is local-only for Rust, Go, JavaScript, and Lua, missing inter-procedural
CALLSedges - Incremental re-indexing restores most inbound edges but leaves residual divergence in deep type chains
- Cross-project resolution is validated on synthetic fixtures, not production monorepos
- Tree-sitter vs. compiler front-end presents a speed-fidelity-recall trade-off with no universally optimal setting
Frequently Asked Questions
Does Code-Graph-RAG require compilation to build the knowledge graph?
No. Code-Graph-RAG operates without compilation using Tree-sitter parsers, which enables its language-agnostic design. However, this same design choice causes the pre-processor blindness and dynamic construct limitations—trade-offs explicitly accepted for portability and speed according to the evals/README.md documentation.
Why does Code-Graph-RAG miss some function calls in my Rust project?
The Rust type inference implementation in parsers/rust/type_inference.py handles only local variable types, not inter-procedural propagation. If your code calls methods on values returned from functions without explicit type annotations, cgr may fail to resolve the receiver type and omit the CALLS edge. Enable --typed mode for best results, though full inter-procedural inference remains unimplemented.
How accurate is incremental re-indexing compared to full re-indexing?
Incremental re-indexing restores most inbound edges from unchanged callers but maintains a documented "small tail of divergence" for complex type-inference scenarios. For critical analysis requiring complete accuracy, full re-indexing remains the safer choice despite longer processing time. The divergence is tracked in evals/README.md lines 81-87 as a known operational constraint.
Can Code-Graph-RAG handle dynamically typed languages like Python accurately?
Python receives among the most mature support, but dynamic resolution through getattr, setattr, or runtime code generation remains invisible to cgr's static analysis. The evaluation framework treats this as an inherent recall ceiling rather than a solvable bug—true dynamic edge detection would require runtime instrumentation beyond cgr's architecture.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →