# How to Detect Dead Code Using Code-Graph-RAG: A Complete Guide

> Learn to detect dead code with Code-Graph-RAG. Build a call graph in Memgraph and use the cgr dead-code command to find unreachable symbols. A complete guide for developers.

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

---

**To detect dead code with Code-Graph-RAG, build a call graph of your repository in Memgraph, then run `cgr dead-code` to find symbols unreachable from any entry point, test, or public API.**

Dead code increases maintenance burden, compile times, and security surface area. Code-Graph-RAG solves this by combining Tree-sitter parsing with graph-based reachability analysis. This guide walks through the mechanism, CLI usage, Python API, and CI integration for the `vitali87/code-graph-rag` open-source tool.

## How Dead Code Detection Works in Code-Graph-RAG

Code-Graph-RAG detects dead code through a four-stage pipeline implemented in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py):

### Stage 1: Graph Construction

The tool parses every source file with Tree-sitter, storing **nodes** (functions, methods, classes, modules) and **edges** (`CALLS`, `REFERENCES`, `INSTANTIATES`, `INHERITS`) in a Memgraph knowledge graph.

The parser lives in the `codebase_rag` package. Schema constants are defined in [`codebase_rag/constants.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants.py).

### Stage 2: Root Identification

Detection starts from *roots*—symbols guaranteed to be reachable. The `_is_root` function at line 91 in [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py) combines multiple rule thunks to identify:

- Exported/public symbols
- Test symbols (unless disabled with `--no-include-tests`)
- Symbols with well-known framework decorators
- Dunder methods (`__init__`, `__call__`, etc.)
- Language-specific entry points (`main` in C/C++, Rust runtime hooks, Java serialization methods)

You can extend roots with custom entry points and decorators via CLI flags.

### Stage 3: Reachability Walk

The private `_walk` helper at line 77 performs a breadth-first traversal following:

- `CALLS` and `REFERENCES` (always)
- `INSTANTIATES` and `INHERITS` (when `--classes` is enabled)

All visited symbols are marked *live*.

### Stage 4: Dead Code Report

After the walk, unvisited symbols are considered dead. The `dead_code_from_graph` function at line 90 filters results by user-provided `--exclude` globs and returns the final set.

## Quick Start: CLI Workflow

Run dead code detection in three steps:

```bash

# 1. Start the Memgraph+Qdrant stack (once per machine)

cgr daemon up

# 2. Index your repository

cgr start --repo-path /path/to/project --update-graph --clean

# 3. Detect dead code

cgr dead-code

```

## CLI Options for Dead Code Detection

The `cgr dead-code` command (implemented in [`evals/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/evals/dead_code.py)) supports fine-grained control:

| Option | Purpose |
|--------|---------|
| `-e / --entry-point <suffix>` | Treat symbols ending with value as roots (e.g., `-e main`) |
| `--decorator-root <name>` | Treat decorated symbols as roots (e.g., `--decorator-root celery_app.task`) |
| `--exclude <glob>` | Exclude files matching glob after the walk; quote globs with wildcards |
| `--include-tests` / `--no-include-tests` | Toggle test code as roots (default: **on**) |
| `--classes` | Include unreachable classes in report (default: **off**) |
| `--format json\|table` | Choose output format |
| `--fail-on-found` | Exit status 1 if any dead code found (CI-friendly) |
| `-o / --output <file>` | Write report to file instead of stdout |

### Practical CLI Examples

Find dead code excluding generated files, failing CI if found:

```bash
cgr dead-code \
  --format json \
  --output dead-code.json \
  --fail-on-found \
  --exclude '*_generated*'

```

Treat Flask route handlers as entry points:

```bash
cgr dead-code --decorator-root flask.app.route --decorator-root flask.blueprints.Blueprint.route

```

Include class reachability analysis:

```bash
cgr dead-code --classes --format table

```

## Detecting Dead Code with the Python API

For programmatic access, import from `codebase_rag.dead_code`:

```python
from codebase_rag.dead_code import dead_code_from_graph, default_dead_code_config
from codebase_rag.types_defs import DeadCodeConfig

# `ingestor` is a GraphQueryClient with loaded graph data

nodes, rels = ingestor.nodes, ingestor.rels

config: DeadCodeConfig = default_dead_code_config(
    include_tests=False,
    include_classes=False,
    exclude_patterns=("*/generated/*", "*/vendor/*")
)

dead_symbols = dead_code_from_graph(
    nodes,
    rels,
    project_prefix="mycompany.",  # filter to your namespace

    config=config
)

print("Dead symbols:", sorted(dead_symbols))

```

**Key parameters in `DeadCodeConfig`:**
- `include_tests`: Whether test functions/methods seed the reachability walk
- `include_classes`: Whether to walk `INSTANTIATES` and `INHERITS` edges
- `exclude_patterns`: Glob patterns applied post-analysis to filter results

## CI/CD Integration

Integrate dead code detection into pipelines to prevent accumulation:

```bash
#!/bin/bash
set -e

cgr daemon up --detach

cgr start \
  --repo-path . \
  --update-graph \
  --clean

cgr dead-code \
  --fail-on-found \
  --exclude '*/migrations/*' \
  --exclude '*/tests/fixtures/*' \
  --output dead-code-report.json

echo "No dead code detected"

```

The `--fail-on-found` flag ensures builds fail when unreachable code exists, enforcing cleanup before merge.

## Key Source Files

| File | Role |
|------|------|
| [`codebase_rag/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/dead_code.py) | Core engine: `dead_code_from_graph`, `_is_root`, `_walk` |
| [`evals/dead_code.py`](https://github.com/vitali87/code-graph-rag/blob/main/evals/dead_code.py) | CLI command implementation (`cgr dead-code`) |
| [`codebase_rag/constants.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/constants.py) | Graph schema constants (`CALLS`, `REFERENCES`, etc.) |
| [`docs/guide/dead-code.md`](https://github.com/vitali87/code-graph-rag/blob/main/docs/guide/dead-code.md) | Official user documentation |

## Summary

- **Graph-first approach**: Code-Graph-RAG uses Tree-sitter + Memgraph to model call relationships precisely
- **Configurable roots**: Entry points, tests, decorators, and public APIs define the live code boundary
- **Fast analysis**: Reachability runs entirely on the graph without re-parsing source files
- **Multiple interfaces**: Use `cgr dead-code` for automation or `dead_code_from_graph()` for custom workflows
- **CI-ready**: `--fail-on-found` and `--format json` integrate with GitHub Actions, GitLab CI, and Jenkins

## Frequently Asked Questions

### What programming languages does Code-Graph-RAG support for dead code detection?

Code-Graph-RAG supports any language Tree-sitter can parse. The `CALLS`, `REFERENCES`, `INSTANTIATES`, and `INHERITS` edges are language-agnostic, while language-specific root detection handles entry points like `main` in C/C++, Rust runtime hooks, and Java serialization methods automatically.

### Can I exclude specific files or directories from dead code analysis?

Yes. Use the `--exclude <glob>` flag multiple times to filter paths after the reachability walk. Quote globs containing wildcards to prevent shell expansion: `--exclude '*/node_modules/*'` or `--exclude 'build/**'`.

### Why does Code-Graph-RAG include test code as roots by default?

Tests often exercise code that no production entry point reaches. Including tests as roots prevents false positives on test utilities and fixtures. Disable with `--no-include-tests` to find production-only dead code.

### How does dead code detection differ from a simple "unused function" linter?

Traditional linters perform static analysis on single files or limited cross-file scope. Code-Graph-RAG builds a complete call graph across your entire codebase including cross-module references, inheritance chains, and decorator-based registration—catching dead code that file-scoped tools miss.