The function passed its test. The application never called it.

Dan Greer · · 5 min read

The new function returns the right answer. Its test passes. Running the application still gives the old result.

That is an easy mistake to make when a coding agent handles a feature in pieces. One step creates the implementation, another adds a test, and the change that connects it to a real application path gets lost between them.

I build Pharaoh, and this is one of the structural problems its PR Guard checks. Here is a small example you can run without Pharaoh, followed by what the actual classifier reported when I ran the same wiring pattern through it.

This is a controlled example, with a stubbed main-branch graph. It is not a customer incident or a capture from a live GitHub pull request.

A useful function, sitting on its own

Suppose an application needs a weekly digest total. The new implementation lives in src/digest.ts:

export function formatWeeklyDigest(rows: number[]): number {
  return rows.reduce((sum, row) => sum + row, 0);
}

The name is deliberately specific enough to find again. Its behavior is small enough to check without building a service around it. Put this in src/digest.test.ts:

import assert from "node:assert/strict";
import { formatWeeklyDigest } from "./digest.js";

export function checksDigest(): boolean {
  return formatWeeklyDigest([2, 3]) === 5;
}

assert.equal(checksDigest(), true);

Run it with a TypeScript runner:

npx tsx src/digest.test.ts

It exits successfully. That assertion proves the function returns five for this input. There is no assertion here about what the application does with it.

The application's existing handler is in src/routes.ts:

export function handleRequest(): string {
  return "summary";
}

And the tiny executable entry in src/index.ts calls that handler:

import { handleRequest } from "./routes.js";

console.log(handleRequest());

Run npx tsx src/index.ts and the result is still summary. Nothing in the production path imports the new function. Its only caller lives in the test file.

An agent can explain every line of the implementation, improve the test cases, or rename the helper, and none of that will deliver the new behavior until something the application executes reaches it.

What the structural check sees

PR Guard runs on Pro workspaces and posts a GitHub check on the pull request. Its findings are advisory by default: they post as a neutral check that doesn't block the merge. It checks new dead code, breaking signature changes with existing callers, exact function duplicates, and the blast radius of changed functions.

For this example, I supplied handleRequest as a known reached function in a stubbed graph and ran Pharaoh's real parser, import resolver and PR Guard classifier over the source files. The classifier reported formatWeeklyDigest as unreachable.

The parser found a caller, but it was the test helper. The production-reference check excludes test files, so a passing test did not turn into evidence that the application used the function.

This is useful review information. It directs me to a question the unit test never asked: where should the production call come from?

Connect it, then run the application again

Replace src/routes.ts with:

import { formatWeeklyDigest } from "./digest.js";

export function handleRequest(): number {
  return formatWeeklyDigest([2, 3]);
}

The unit test still passes. Running the entry now prints 5.

There is also a path through the application. This is a schematic of the example, rather than tool output:

index module
  -> handleRequest
     -> formatWeeklyDigest

When I reran the classifier on that version, it reported the function as reachable.

The function body did not change. The change was in its production caller, which is exactly the part I want a reviewer to notice when an implementation and its tests already look convincing.

A reference is weaker evidence than a call path

For a third control, I removed the production call and made the handler return the string "formatWeeklyDigest". The classifier then reported likely_reachable, with the note that a production reference existed but no graph path had been found.

That conservative result is intentional. Runtime registries and framework conventions can use strings or configuration in ways static analysis cannot fully resolve. Treating every missing path as a defect would create findings that developers learn to ignore.

It also creates a limit worth understanding: an unrelated string can stop this particular unreachable-export warning. The check is evidence for review, and a likely_reachable verdict still leaves you with source to inspect.

Version Unit assertion Application output Classifier verdict
Test calls the new function Passes summary unreachable
Production handler calls it Passes 5 reachable
Production code only names it as a string Passes formatWeeklyDigest likely_reachable

Pharaoh's get_unused_code MCP tool uses different categories. It reports Dead only when no entry point reaches a function, no other file names it, and no production file imports its file.

It reports Possibly unused when the function is unreached but named elsewhere or its file is imported. Used only by tests identifies exported functions referenced only by test files.

A separate guide explains what counts as dead code.

See dead-code detection an agent can call over MCP for the interactive workflow.

What this does and does not establish

A reachable function can still contain a bug. It can receive the wrong arguments, run behind the wrong permission check or produce a response the client never displays. An integration test through the actual public interface is how I check the behavior a user should receive.

Reachability answers an earlier question: does a detected production path get here at all?

There are also legitimate exports with no ordinary caller. A library's public API, a plugin hook or an entry configured by a framework may need an explicit entry declaration or an intentional-orphan allowance. Review the loading mechanism before changing code to satisfy a warning, and keep those declarations narrow enough that they explain the specific case.

The interactive MCP map describes the mapped default branch; asking it about an unmerged feature does not substitute for PR Guard's check on the pull request.

For existing exports, another guide covers finding unused code in a TypeScript project.

Habits that prevent orphaned code

Start with the entry point. Before asking an agent to fill in the implementation, identify the route or command that should reach it, wire the smallest working version, and check the result from that entry.

In this example, the missing connection is in src/routes.ts. Adding more assertions directly against formatWeeklyDigest would leave that connection missing.

Keep the diff small enough to review the caller alongside the implementation. Ask for one working path, run its test, inspect the production call, and fix that path before asking for the next part of the feature.

For a feature with a written requirement, put the expected behavior beside the entry-point test.

The guide to features specified but never wired in carries that check back to the requirement.

The review habit I want to keep

When an agent adds an exported function, I ask it to identify the production caller and show a test through the feature's entry point. That request often reveals a missing connection before anyone needs a structural tool.

PR Guard adds another opportunity to catch the omission. It can bring the orphan export into the review while the patch is still small, the intended caller is still obvious, and the person making the change remembers what the feature was supposed to do.

The four files above are enough to reproduce the behavior. The feature-wiring docs cover PR Guard and its analysis scope. To use the structural check on your own pull requests, connect Pharaoh; PR Guard requires Pro and is advisory by default. Start by inspecting a finding's caller evidence, then verify the feature through the application path that should use it.

More in Dead Code

← Back to blog