# How Code-Graph-RAG Detects Dead Code Using Its Knowledge Graph

> Discover how Code-Graph-RAG detects dead code. It builds a knowledge graph and uses BFS to identify unreachable symbols, streamlining your codebase.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-19

---

**Code‑Graph‑RAG detects dead code by constructing a knowledge graph of symbols and relationships, then performing a breadth‑first search from identified entry points (roots) to mark reachable symbols; any symbol not reached is flagged as dead.**

The `vitali87/code-graph-rag` repository provides a static analysis engine that represents your codebase as a rich knowledge graph. By traversing this graph from known entry points, the engine in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py) accurately identifies unreachable functions, methods, and classes across multiple programming languages.

## How the Knowledge Graph Powers Dead Code Detection

Code‑Graph‑RAG models every symbol—functions, methods, classes, and modules—as **nodes** in a graph. The ingest stage (`cgr_graph._capture`) extracts these symbols and their interconnections, storing them as edges that capture relationships such as **CALLS**, **REFERENCES**, **INSTANTIATES**, **INHERITS**, **DEFINES**, **OVERRIDES**, and more.

Dead code detection treats this graph as a reachability problem. The algorithm determines which symbols are reachable from any **root** (entry point) in the project. Symbols that remain unvisited after the traversal constitute the dead code set.

## Identifying Entry Points (Roots) with Language‑Aware Heuristics

The engine gathers entry points through a comprehensive set of language‑specific heuristics defined in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py). These roots serve as the starting nodes for the reachability traversal.

**Decorator and Annotation Detection**

The `_has_root_decorator` function normalizes decorator names and matches them against predefined sets (`DEFAULT_ROOT_DECORATORS`). This catches framework entry points like Flask routes or pytest fixtures.

**Exported and Protocol Symbols**

Symbols marked with `props.get(cs.KEY_IS_EXPORTED)` are treated as public APIs. For Python, the engine also identifies `typing.Protocol` stubs as roots since they define interface contracts that external code may implement.

**Language‑Specific Runtime Entry Points**

The engine recognizes canonical entry points across languages:

- **Python**: Dunder methods (`_is_dunder`), Enum hooks (`PY_ENUM_HOOK_METHOD_NAMES`), and framework patterns (NestJS `@Injectable`, React lifecycle methods via `_is_react_root`)
- **Go**: `main` and `init` functions (`GO_ROOT_FUNCTION_NAMES`)
- **Rust**: `fn main()` and trait methods (`_is_rust_runtime_root`)
- **C/C++**: `main`, `WinMain`, `DllMain`, and operator overloads (`_is_c_cpp_entry_root`, `_is_cpp_operator_root`)
- **Java**: Serialization hooks like `readObject` (`_is_java_serialization_root`)
- **C#**: Attributes such as `[Fact]` and dispose patterns (`_is_csharp_attribute_root`, `_is_csharp_dispose_root`)
- **JavaScript/TypeScript**: Well‑known symbols (`[Symbol.iterator]`) via `_is_js_well_known_symbol_root`

**Custom Entry Points**

Users can provide additional roots via the `entry_points` configuration parameter. The engine checks if a symbol’s fully‑qualified name ends with any user‑specified entry string.

## The Dead Code Detection Algorithm

The `dead_code_from_graph` function implements a multi‑phase analysis pipeline.

### Candidate Selection

The engine first builds a `candidates` set containing all symbols whose fully‑qualified name starts with the project prefix. By default, test symbols are filtered out via `_is_test_symbol` logic unless `include_tests=True` is set in the configuration.

### Graph Traversal and Reachability

The core traversal occurs in the `_walk` function, which executes a breadth‑first search starting from the collected roots. The walker follows **CALLS** and **REFERENCES** edges (defined as `_CALLS` and `_REFERENCES` in the traversal set) to discover all transitively reachable symbols, which are added to the `live` set.

### Closure and Factory Handling

After the initial pass, the engine performs a second phase to handle indirect relationships:

- **Closure roots**: Any symbol connected via a **DEFINES** edge to an already‑live owner (such as decorator‑generated functions) is added to the roots and re‑traversed.
- **Factory classes and overrides**: The algorithm iteratively expands factory‑class relationships and override hierarchies until no new symbols are discovered (lines 182–224 in [`dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/dead_code.py)).

### Final Dead Set Calculation

The dead code set is computed as `candidates – live`. Optionally, the `exclude_patterns` configuration filters out generated files or specific paths before producing the final report.

## CLI and Programmatic Usage

You can invoke the dead code detector via the command line or directly from Python.

**Programmatic API**

```python
from codebase_rag.dead_code import dead_code_from_graph, default_dead_code_config
from codebase_rag.constants import NodeLabel, RelationshipType

# Assume `nodes` and `rels` come from a GraphQueryClient

project_prefix = "myproj."
config = default_dead_code_config(
    include_tests=False,
    include_classes=False,
    exclude_patterns=("*/generated/*",)
)

dead_qns = dead_code_from_graph(nodes, rels, project_prefix, config)

for qn in sorted(dead_qns):
    print(f"Dead: {qn}")

```

**Command Line Interface**

```bash
cgr dead-code \
  --target path/to/cgr/report \
  --project myproj \
  --include-tests \
  --exclude "**/generated/**"

```

The CLI writes results to [`dead_code.diff.json`](https://github.com/vitali87/code-graph-rag/blob/main/dead_code.diff.json) (or the path specified by `--output`), containing a sorted list of fully‑qualified dead symbol names.

## Summary

- Code‑Graph‑RAG constructs a **knowledge graph** where nodes are symbols and edges represent relationships like **CALLS** and **REFERENCES**.
- The engine identifies **roots** using 15+ language‑specific heuristics in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py), covering decorators, main functions, protocol stubs, and framework patterns.
- A **breadth‑first search** (`_walk`) traverses from roots over **CALLS** and **REFERENCES** edges to mark live code.
- **Closure and factory passes** handle indirect definitions and inheritance hierarchies in a second iterative phase.
- The final dead set is the difference between project candidates and reachable symbols, optionally filtered by exclude patterns.

## Frequently Asked Questions

### What types of relationships does Code‑Graph‑RAG traverse to find reachable code?

The engine primarily traverses **CALLS** and **REFERENCES** edges during the initial reachability phase. In subsequent passes, it also considers **DEFINES** relationships to capture decorator‑generated functions and closure patterns, ensuring indirect references are not falsely flagged as dead.

### How does Code‑Graph‑RAG handle dynamic entry points like dependency injection frameworks?

The root detection logic includes specific heuristics for frameworks such as **NestJS** (`@Injectable`, `@Controller` via `_is_nest_root`) and **React** (lifecycle methods via `_is_react_root`). These functions check for framework‑specific decorators, base classes, and metadata to identify symbols that serve as implicit entry points despite not being directly invoked in source code.

### Can I exclude test files from dead code detection in Code‑Graph‑RAG?

Yes. By default, the `_is_test_symbol` logic filters out test files. You can control this behavior via the `include_tests` parameter in `DeadCodeConfig` or the `--include-tests` CLI flag if you want to treat test code as valid entry points for reachability analysis.

### What file contains the core dead code detection logic?

The core algorithm resides in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py). This file contains the `dead_code_from_graph` function, the `_walk` traversal implementation, and all language‑specific root detection heuristics. The CLI wrapper is located in [`evals/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/evals/dead_code.py), which handles graph ingestion and configuration setup.