How to Find Dead Code Using code-graph-rag: Graph-Based Detection Guide
You can find dead code using code-graph-rag by initializing a GraphQueryClient, configuring analysis settings with default_dead_code_config(), and executing collect_dead_code() to identify symbols unreachable from defined root entry points.
Finding dead code in large, multi-language codebases requires tracing call relationships across language boundaries and framework patterns. The code-graph-rag repository provides a dedicated reachability engine in codebase_rag/dead_code.py that performs graph-based analysis to detect unused symbols. This guide explains how to leverage this engine to find dead code across Python, Rust, Java, C++, and other supported languages.
How the Dead Code Detection Engine Works
The dead code engine operates by building a reachability graph from CALLS and REFERENCES edges stored in the graph database. According to the implementation in codebase_rag/dead_code.py, the analysis follows a three-phase process:
-
Root Identification (lines 15-94): The engine scans candidate nodes to identify roots—entry points like framework hooks, exported symbols, test decorations, and language-specific runtime symbols. Helper functions like
_is_dunder,_is_rust_runtime_root, and_has_root_decoratorhandle patterns across Python, Rust, Java, C#, JavaScript/TypeScript, and NestJS. -
Graph Traversal (lines 75-89): A breadth-first search walks the adjacency map starting from identified roots, marking all reachable symbols as live code.
-
Factory and Override Expansion (lines 133-170): After the initial walk, the engine performs fixed-point iteration over override relationships and factory-class mappings to ensure polymorphic calls and dependency injection roots are properly traced.
Any symbol not visited during this process is reported as dead code, with results filtered through user-defined exclusion patterns.
Step-by-Step: Finding Dead Code with the Python API
To programmatically find dead code using code-graph-rag, instantiate a query client and invoke the analysis pipeline.
Initialize the Graph Query Client
First, create a GraphQueryClient connected to your graph database (Memgraph or the in-memory test harness):
from codebase_rag.graph_query import GraphQueryClient
client = GraphQueryClient(
uri="bolt://localhost:7687",
user="neo4j",
password="*****"
)
Configure Root Detection and Exclusions
Use default_dead_code_config() (defined at lines 41-53 in codebase_rag/dead_code.py) to create a DeadCodeConfig object that controls whether test code and class symbols are treated as roots, and to specify exclusion patterns:
from codebase_rag.dead_code import default_dead_code_config
cfg = default_dead_code_config(
include_tests=False,
include_classes=False,
exclude_patterns=(
"**/node_modules/**",
"**/generated/**",
)
)
Execute the Analysis
Call collect_dead_code() (public API at lines 61-69) to retrieve unreachable symbols. The function returns rows containing the fully-qualified name and source path for each dead symbol:
from codebase_rag.dead_code import collect_dead_code
dead_rows = collect_dead_code(
client=client,
project_name="myproj",
config=cfg
)
for row in dead_rows:
print(f"{row['qualified_name']} → {row['path']}")
The cgr_dead_code() wrapper in evals/dead_code.py provides a convenience interface used by the CLI, but collect_dead_code() exposes the full API for custom integrations.
Running Dead Code Analysis from the Command Line
For CI/CD integration or quick checks, use the cgr CLI entry point defined in codebase_rag/graph_cli.py. The dead-code subcommand wraps the same Python API:
cgr dead-code \
--target /path/to/my/project \
--project-name myproj \
--exclude-patterns "**/generated/**" \
--include-tests false \
--include-classes false
The CLI handles argument parsing, constructs the DeadCodeConfig, and prints a formatted table of dead symbols identified by the reachability engine.
Customizing Root Detection for Framework-Specific Patterns
The engine recognizes language-specific roots without manual configuration. In codebase_rag/dead_code.py, the detection logic handles:
- Python: Dunder methods and test decorators
- Rust: Runtime roots and test attributes
- Java: Serialization hooks and entry functions
- C/C++: Main entry points and library exports
- C#: Attributes and framework hooks
- JavaScript/TypeScript: NestJS DI roots and React component lifecycle methods
You can extend this behavior by modifying the _is_*_root helper functions in the source, or by using exclude_patterns in the configuration to filter generated or vendored code that should not be analyzed for liveness.
Summary
- Find dead code using code-graph-rag by querying unreachable nodes from a graph of CALLS and REFERENCES edges.
- The
collect_dead_code()function incodebase_rag/dead_code.pyimplements a breadth-first search from identified roots, handling polymorphism through override and factory-class expansion. - Configure analysis with
default_dead_code_config()to control test inclusion, class symbol handling, and exclusion patterns. - Use the CLI command
cgr dead-codefor command-line integration or the Python API for programmatic analysis. - The engine supports Python, Rust, Java, C++, C#, and JavaScript/TypeScript through language-specific root detection helpers.
Frequently Asked Questions
What constitutes a "root" in dead code detection?
A root is any symbol that serves as a legitimate entry point for execution or external reference. According to the implementation in codebase_rag/dead_code.py (lines 15-94), roots include framework hooks like React lifecycle methods, NestJS DI providers, exported library symbols, test functions decorated with test attributes, and language-specific runtime entry points like main functions or Python dunders.
How does code-graph-rag handle inheritance and factory patterns?
After the initial graph walk, the engine performs additional traversal phases (lines 133-170 in codebase_rag/dead_code.py) that specifically handle override relationships and factory-class mappings. This ensures that if a base class method is called through polymorphism, or if a factory instantiates objects via dependency injection, those symbols are marked as reachable even if not directly invoked in static analysis.
Can I include test files in the dead code analysis?
Yes. The include_tests parameter in default_dead_code_config() controls whether test-related decorations and test files are considered roots. Setting include_tests=True treats test entry points as live code, which prevents test-only utilities from being flagged as dead. Set it to False when you want to identify dead code within the test suite itself.
Which graph databases are supported for dead code analysis?
The GraphQueryClient used by collect_dead_code() supports Memgraph via Bolt protocol and an in-memory test harness used for evaluation. The URI connection string in the client initialization determines the backend, allowing integration with existing Memgraph deployments or lightweight local analysis without external database dependencies.
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 →