How Dead Code Detection Works in Codebase-Memory-MCP: Graph Analysis Explained

Codebase-Memory-MCP identifies dead code by querying inbound degrees of CALLS and USAGE edges in its SQLite-backed dependency graph, classifying functions with zero incoming connections as dead while exempting tests, entry points, and exported symbols.

Codebase-Memory-MCP (MCP) builds a complete graph representation of your codebase where functions, methods, and classes serve as nodes connected by semantic edges. Dead code detection operates during the 3D layout phase in src/ui/layout3d.c, where the system analyzes the full graph to determine which functions are reachable and which are orphaned.

The Dead Code Classification Matrix

MCP applies a strict hierarchy of rules to classify each node during the layout phase. The classification depends on node type, metadata flags, and inbound connection counts from the graph store:

  • Structural: Non-function and non-method nodes (classes, variables, etc.)
  • Test: Nodes located in test files (identified by is_test flags or path heuristics)
  • Entry: Entry points marked with is_entry_point or route_path flags
  • Exported: Symbols explicitly marked with is_exported in their properties
  • Dead: Functions or methods with zero inbound CALLS and zero inbound USAGE
  • Single: Functions or methods with exactly one inbound CALLS edge
  • Normal: All other function/method nodes with multiple connections

Dead Code Detection Implementation in layout3d.c

The dead code detection algorithm executes in five distinct phases within the layout engine.

1. Node Collection and Batch Preparation

The system first collects all node IDs from the current search result set to prepare for batch degree queries:

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;

To avoid SQLite parameter limits, the implementation processes nodes in chunks defined by DEAD_DEGREE_CHUNK.

2. Batch Querying Inbound Degrees

The layout engine queries the store for inbound degrees of both CALLS and USAGE edge types using cbm_store_batch_count_degrees:

/* Allocate buffers for inbound CALLS and USAGE counts */
int *in_calls = calloc((size_t)n, sizeof(int));
int *in_usage = calloc((size_t)n, sizeof(int));

/* Query the store in chunks to avoid SQL limits */
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);
}

This batch approach minimizes database round-trips by building bounded IN (?, ?, …) clauses.

3. Parsing Node Metadata Flags

Before classification, the system parses node-specific flags from the JSON properties_json stored with each node:

node_flags_t nf = parse_node_flags(sn->properties_json);

These flags indicate whether the node represents a test file, entry point, exported symbol, or route.

4. Applying the Decision Logic

The core classification logic applies the decision matrix through a cascading conditional chain:

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
int iu = in_usage ? in_usage[i] : 1;   // inbound USAGE

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. Error Handling and Fallbacks

If the batch query fails due to SQLite errors, the code defaults to "normal" status rather than incorrectly flagging code as dead. This safety mechanism ensures transient store problems do not result in false dead code positives.

Store-Level Queries for Dead Code Detection

The heavy lifting occurs in cbm_store_batch_count_degrees, implemented in src/store/store.c. This function constructs parameterized SQL queries to count degrees efficiently:

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)
{
    /* ... build the 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 function calls count_degrees_direction twice—once for inbound and once for outbound edges—to populate the degree buffers.

Data Pipeline and Edge Creation

The dead code detection relies on CALLS edges created during the analysis pipeline. The file src/pipeline/pass_calls.c resolves function calls and inserts these edges into the graph, providing the connectivity data that the layout phase later queries to determine liveness.

Summary

  • Graph-Based Analysis: MCP detects dead code by examining inbound CALLS and USAGE degrees in the complete dependency graph.
  • Dual Edge Types: A function is only dead if both CALLS and USAGE inbound counts equal zero.
  • Exclusion Logic: Tests, entry points, routes, and exported symbols are explicitly protected from dead code classification.
  • Batch Processing: Degree queries use chunked batch operations via cbm_store_batch_count_degrees to handle large codebases efficiently.
  • Safe Defaults: Store errors default to "normal" status, preventing false dead code reports.

Frequently Asked Questions

How does Codebase-Memory-MCP define dead code?

Codebase-Memory-MCP defines dead code as any function or method node in the dependency graph that has zero inbound CALLS edges and zero inbound USAGE edges. This means no other function calls it, and no other part of the codebase references it through usage relationships.

What prevents entry points and tests from being marked as dead?

The system checks node metadata flags parsed from properties_json before applying degree-based rules. Nodes marked with is_entry_point, route_path, is_exported, or test file heuristics receive "entry", "exported", or "test" statuses respectively, bypassing the dead code classification regardless of their inbound degree counts.

Where does the dead code detection logic reside in the codebase?

The primary classification logic lives in src/ui/layout3d.c within the layout phase implementation. The underlying degree counting queries are handled by cbm_store_batch_count_degrees in src/store/store.c, while src/pipeline/pass_calls.c generates the CALLS edges that feed the detection algorithm.

What happens if the SQLite query fails during dead code detection?

If cbm_store_batch_count_degrees returns an error, the layout engine falls back to assigning a "normal" status to affected nodes. This conservative approach ensures that temporary database issues or connectivity problems do not cause the system to falsely report functioning code as dead.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →