# How Dead Code Detection Works in Codebase-Memory-MCP: Analyzing the Dependency Graph

> Discover how DeusData/codebase-memory-mcp detects dead code by analyzing the dependency graph. Learn how it handles edge cases like entry points and exports.

- Repository: [Martin Vogel/codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)
- Tags: deep-dive
- Published: 2026-07-16

---

**Codebase-Memory-MCP identifies dead code by querying inbound `CALLS` and `USAGE` edges in its SQLite-backed dependency graph, marking functions as dead only when they have zero incoming calls and zero usage references while explicitly excluding entry points, exported symbols, and test files.**

Codebase-Memory-MCP (MCP) constructs a complete dependency graph of your codebase, persisting nodes for functions, methods, and classes alongside their relationships in an SQLite store. The **dead code detection** system operates during the 3D layout phase in [`src/ui/layout3d.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/ui/layout3d.c), batch-querying the graph to identify functions that are never invoked or referenced by any other part of the system. This analysis reveals how MCP distinguishes between truly dead code and intentionally exposed entry points through a strict priority-based classification matrix.

## The Dead Code Classification Matrix

MCP applies a hierarchical decision tree when labeling nodes, checking conditions in strict priority order to prevent false positives:

| Condition | Status Assigned |
|-----------|----------------|
| Not a function or method | `structural` |
| Test file (`is_test` flag or path heuristic) | `test` |
| Entry point (`is_entry_point`) or route (`route_path`) | `entry` |
| Exported symbol (`is_exported`) | `exported` |
| Zero inbound `CALLS` **and** zero inbound `USAGE` | `dead` |
| Exactly one inbound `CALL` | `single` |
| All other functions/methods | `normal` |

This matrix ensures that **public APIs** and **application entry points** are never mistakenly flagged as dead code, even if they have low internal usage within the analyzed subgraph.

## Implementation in src/ui/layout3d.c

The classification logic resides in the layout phase where the UI prepares the graph for rendering. The process executes in five distinct steps:

### 1. Collect Node Identifiers

First, the system aggregates node IDs from the current search result set:

```c
int64_t *node_ids = malloc((size_t)n * sizeof(int64_t));
for (int i = 0; i < n; i++) {
    node_ids[i] = search_out.results[i].node.id;
}

```

### 2. Batch Query Inbound Degrees

To avoid SQLite parameter limits, MCP queries degrees in configurable chunks using `DEAD_DEGREE_CHUNK`:

```c
int *in_calls = calloc((size_t)n, sizeof(int));
int *in_usage = calloc((size_t)n, sizeof(int));

for (int off = 0; off < n; off += DEAD_DEGREE_CHUNK) {
    int cnt = (n - off < DEAD_DEGREE_CHUNK) ? (n - off) : DEAD_DEGREE_CHUNK;
    cbm_store_batch_count_degrees(store,
        node_ids + off, cnt, "CALLS", in_calls + off, deg_dummy + off);
    cbm_store_batch_count_degrees(store,
        node_ids + off, cnt, "USAGE", in_usage + off, deg_dummy + off);
}

```

### 3. Parse Node Metadata Flags

The system extracts semantic metadata from the JSON `properties_json` field to identify special categories:

```c
node_flags_t nf = parse_node_flags(sn->properties_json);

```

### 4. Apply Decision Logic

The core classification compares inbound call counts (`ic`) against usage counts (`iu`):

```c
bool is_fn = sn->label && (strcmp(sn->label, "Function") == 0 ||
                           strcmp(sn->label, "Method") == 0);
int ic = in_calls ? in_calls[i] : 1;   // inbound CALLS count
int iu = in_usage ? in_usage[i] : 1;   // inbound USAGE count

const char *status;
if (!is_fn)                          status = "structural";
else if (testish)                    status = "test";
else if (nf.is_entry || nf.is_route) status = "entry";
else if (nf.is_exported)             status = "exported";
else if (ic == 0 && iu == 0)          status = "dead";
else if (ic == 1)                    status = "single";
else                                  status = "normal";

result->nodes[i].status = status;

```

### 5. Store Results for Frontend

The final classification is attached to the layout result for visualization:

```c
result->nodes[i].status = status;

```

## The Store Layer and Batch Queries

The heavy lifting occurs in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) within `cbm_store_batch_count_degrees`. This function constructs bounded `IN` clauses to query edge counts efficiently without hitting SQLite's parameter limits:

```c
int cbm_store_batch_count_degrees(cbm_store_t *s,
    const int64_t *node_ids, int id_count,
    const char *edge_type, int *out_in, int *out_out)
{
    /* ... builds IN clause like "(?,?,?)" ... */
    bool has_type = edge_type && edge_type[0] != '\0';

    /* inbound degree */
    int rc = count_degrees_direction(s, node_ids, id_count,
        in_clause, has_type, edge_type, true, out_in);
    if (rc != CBM_STORE_OK) return rc;

    /* outbound degree */
    return count_degrees_direction(s, node_ids, id_count,
        in_clause, has_type, edge_type, false, out_out);
}

```

The underlying `count_degrees_direction` executes the actual SQLite counting operations, filtering by edge type (`CALLS` or `USAGE`) and direction to populate the inbound and outbound degree buffers.

## Edge Cases and Error Handling

MCP handles several critical edge cases to prevent false positives in **dead code detection**:

- **Entry Points and Routes**: Functions marked with `is_entry_point` or containing `route_path` are classified as `entry`, protecting main functions and HTTP handlers from deletion regardless of their inbound degree.
- **Exported Symbols**: Public API surfaces flagged with `is_exported` receive `exported` status, preserving library interfaces that external consumers may invoke.
- **Test Files**: Files identified via `is_test` flags or path heuristics are marked as `test`, acknowledging that test functions and helpers often lack internal callers by design.
- **Query Failure Fallback**: If `cbm_store_batch_count_degrees` returns an error code (anything other than `CBM_STORE_OK`), the code defaults to `"normal"` status rather than `"dead"`. This conservative approach ensures transient SQLite errors never cause developers to delete active code.

## Summary

- **Dead code detection** in codebase-memory-mcp occurs during the UI layout phase in [`src/ui/layout3d.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/ui/layout3d.c), analyzing the complete dependency graph stored in SQLite.
- A function is marked `dead` only when it has **zero inbound `CALLS`** and **zero inbound `USAGE`** edges, indicating it is neither invoked nor referenced by any other node.
- The system explicitly excludes **entry points**, **exported symbols**, **routes**, and **test files** through a priority-ordered decision matrix that checks metadata flags before degree counts.
- Batch queries using `cbm_store_batch_count_degrees` in [`src/store/store.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/store/store.c) efficiently compute degrees while respecting SQL parameter limits through chunked `IN` clauses.
- Error handling defaults to `"normal"` status to prevent false dead-code positives during database failures or connectivity issues.

## Frequently Asked Questions

### What defines "dead code" in codebase-memory-mcp?

In codebase-memory-mcp, dead code refers to functions or methods that have **zero incoming `CALLS` edges** and **zero incoming `USAGE` edges** in the dependency graph. This means no other function invokes them directly, and no other code references them as callbacks, event handlers, or configuration values. The analysis specifically targets the `CALLS` relationships established by [`src/pipeline/pass_calls.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/pipeline/pass_calls.c) and usage patterns tracked throughout the graph topology.

### Does MCP detect dead classes or only functions?

The current implementation in [`src/ui/layout3d.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/ui/layout3d.c) specifically checks for nodes labeled `"Function"` or `"Method"`. Classes, structs, and other structural elements receive a `structural` status by default and are excluded from dead-code evaluation. The `is_fn` boolean check explicitly filters the classification logic to only consider callable code entities, preventing structural declarations from being flagged as dead.

### How does MCP avoid flagging public APIs as dead code?

MCP parses node metadata from `properties_json` to identify **exported symbols** (`is_exported`), **entry points** (`is_entry_point`), and **routes** (`route_path`). These boolean flags take precedence in the decision matrix, assigning `exported` or `entry` status before the dead-code check executes. According to the source code in [`src/ui/layout3d.c`](https://github.com/DeusData/codebase-memory-mcp/blob/main/src/ui/layout3d.c), this ensures public interfaces and application entry points remain classified as active regardless of their actual inbound call count in the analyzed subgraph.

### What happens if the SQLite query fails during dead code analysis?

If `cbm_store_batch_count_degrees` returns an error code (anything other than `CBM_STORE_OK`), the layout engine defaults the node status to `"normal"` rather than `"dead"`. This conservative fallback, implemented in the error handling logic of the layout phase, prevents transient database errors or schema mismatches from causing developers to delete actually-used code. The system prioritizes safety over aggressive dead-code identification.