NxtSoftLabs
← All writing

Your coding agent says it updated every caller. Check.

August 24, 2026·5 min read

When a coding agent finishes a rename and reports "I updated all the call sites," that sentence is a claim, not a fact. Blastline 0.10.0 adds blastline check, which turns it into a question you can answer: resolve the symbol in a CGraph code graph, walk the edges pointing at it, and print what still references it, with file:line. It is built to refute the claim and is deliberately incapable of confirming it.

The question diff-driven tools cannot answer

Blastline's existing commands are diff-driven. blastline tests and blastline blast both start from "here is a change" and answer "what does it reach." That is the right shape for CI, where a diff already exists.

An agent mid-refactor has an earlier and different question: before I finish changing this symbol, does anything else depend on it that I have not accounted for? That question is symbol-driven, and until now the only way to ask it was to open a PR and read the blast-radius comment — which is to say, after the fact.

check is that query, moved before the edit:

blastline check callers <symbol> [--exclude <symbol>...] [--transitive]

The symbol takes file:line, file:label, or a bare label, because those are the three forms an agent actually has on hand.

What it looks like

Blastline on its own source, asking what references nodesInFile:

$ blastline check callers nodesInFile
verdict: refuted
imports    file     src/graph.test.ts   (src/graph.test.ts:1)
CALLS      function resolveSymbol      (src/graph.ts:121)
re_exports file     src/index.ts        (src/index.ts:1)
imports    file     src/mapping.ts      (src/mapping.ts:1)
CALLS      function mapDiffToSeeds     (src/mapping.ts:28)
imports    file     src/seam.test.ts    (src/seam.test.ts:1)

Every row names the relation that produced it. CALLS is a real call site an edit has to account for; imports and re_exports are file-level references that break differently and matter for a rename. --json returns the same thing structured, with the resolved symbol's id, kind, file and line, so an agent does not have to parse the text.

--exclude is what makes it usable mid-edit. An agent that has already updated src/mapping.ts:28 names it, and the question narrows to "is there any other caller":

blastline check callers nodesInFile --exclude src/mapping.ts:28

That is the shape of the claim an agent actually makes. It never says "nothing calls this"; it says "I handled the callers." --exclude asks about the remainder.

The design decision: a falsifier, never a certifier

The load-bearing choice is what check does when it finds nothing.

A code graph's edges are a superset with approximate coverage. Dynamic dispatch, reflection, and macros are invisible to it. So the two outcomes are not symmetric, and Blastline refuses to pretend they are:

  • Callers found beyond the excluded set is an authoritative refutation. These do reference the symbol, here they are, the claim was wrong. This is the valuable direction — it catches the caller the agent was about to break.
  • No callers found returns no-static-callers. That is explicitly not "safe to delete." It is "the graph cannot see one," which is a weaker statement, and the tool says the weaker thing.

This is the same contract the rest of Blastline ships. Test selection returns a superset and fails open rather than certifying a test as skippable. check refutes a claim and declines to bless one. A tool that tells you when you are wrong and stays quiet about when you are right is more useful than one that is confidently wrong in both directions, because you can act on the first without trusting it.

Ask about a symbol that does not exist and you get the same honesty rather than a reassuring empty list:

$ blastline check callers definitelyNotARealSymbol
UNVERIFIED
fail-open: {"kind":"symbol-not-found","symbol":"definitelyNotARealSymbol"}

An empty result and an unresolvable symbol look identical if you only check the exit code. They are very different facts, so they get different output.

For agents, over MCP

The primary consumer is not a human at a prompt. blastline mcp serves blastline_check alongside blastline_tests and blastline_blast:

claude mcp add blastline -- npx blastline mcp

An agent can then pre-flight its own edit against the exact tree it is editing. That last part is not incidental: every answer carries the graph's sha256-merkle-v1 content root as provenance, so a check performed against a stale index is detectable rather than silently reassuring. An agent verifying a refactor against a graph built from yesterday's code is worse than not checking at all, because it produces confidence without grounds.

We found out it works by breaking our own code

The first real use was Blastline's own. A function named nodesForPath was doing something its name did not describe: returning every node in a file, not resolving a path to a node. Renaming it to nodesInFile is exactly the edit where you convince yourself you got all the references.

check found the ones that mattered, including the re_exports edge through src/index.ts that a grep for nodesForPath( would have missed, because the re-export does not call it — it forwards it. The rename shipped as #13 with the verification in the commit that made it.

Where it stops

check inherits the boundary every static tool has, and the boundary is real. We hit the clearest version of it while benchmarking Rust: on ripgrep, reverting a flag change fails 14 tests in tests/index/disallowed.rs. Those tests genuinely depend on the change. But they invoke the built rg binary as a subprocess, not through any function call — so there is no edge to find, and no improvement to the extractor will ever produce one.

That is not a bug to fix. It is the shape of the problem, and it is why check refutes rather than certifies. The graph knows a great deal about what references a symbol. It does not know everything, and a tool that behaves as though it does will eventually cost you more than it saved.

Blastline and CGraph are both open source and MIT-licensed. If you have an agent doing refactors in your repo, the interesting question is not whether it is usually right. It is whether you can tell when it is not.