Blog

Blast Radius Analysis: What Breaks When You Change Code

An agent renames a shared helper, runs the tests it can see, and ships a diff that breaks three services it never looked at. The helper had fourteen callers; grep found nine, missed one routed through an interface, and had no way to know about the four callers living in a different repository. Blast radius, done right, is a call the agent makes mid-session, before it writes the edit, answered in the time it takes for one more tool call.

What blast radius means for an agent

Static-analysis desktop tools built for human architects have scanned dependency graphs overnight for years and produced impact reports for review before a big refactor. That workflow suits a human with a quarter to plan. An agent making dozens of small edits in a single session needs the same answer in milliseconds, as a callable primitive: give it a symbol, get back the direct and transitive dependents, in the time it takes to make one more tool call.

The worked example: renaming a shared helper

Say a shared helper, formatCurrency, needs a signature change: add a required locale parameter. An agent working from grep searches for formatCurrency and gets back every line that contains the string. Some of those are the real call sites. Some are a comment referencing the function by name. One is a call routed through an interface where the concrete implementation is named formatCurrency but the call site only ever references the interface method format, so grep never finds it at all. The agent, working from an incomplete and noisy list, either misses the interface-routed caller and ships a broken build, or plays it safe and touches every string match including the comment, which is its own kind of wrong.

A graph-backed lookup resolves the same question differently. formatCurrency is a node. Every real call to it, including the one routed through the interface, is an edge, because the graph is built by parsing calls and implementations, not by matching strings. Ask for the callers and you get the real set: the direct call sites, resolved through the interface where one exists, with no comments and no false positives. Ask for the blast radius and you get one layer further out: what breaks in the callers of those callers if the signature changes underneath them.

Question Grep on formatCurrency Graph relate(kind: callers)
Direct string matches 9 lines, mixed with comments n/a, resolves symbols not strings
Interface-routed caller Missed entirely Found, resolved through format
Comment mentioning the name Included as a false positive Excluded
Transitive callers of callers Not answered Returned as the blast radius set
Cross-repo callers Not answered Returned when repos are linked

This is the same shape of gap that shows up on every rename, signature change, or delete once a codebase has more than a handful of files and at least one interface in the mix.

Direct vs transitive dependents

Two different questions get asked here, and agents, along with the tools built for them, frequently collapse them into one. "Who calls this function" is a direct-dependents question: one hop out on the call graph. "What breaks if I change this function's behavior" is transitive: it has to walk beyond the immediate callers into whatever depends on those callers in turn, because a function two hops away can depend on behavior that never appears in its own source. A direct-callers answer is cheap and often sufficient for a simple rename. A real blast radius answer walks the full transitive closure and returns it as a set an agent can reason over.

Cross-repo blast radius

The hardest version of this question crosses a repository boundary. A shared library ships from its own repo; two backend services and a CLI tool consume it from three other repos. Change a public method's behavior in the library and the blast radius extends into every consumer repo that calls that method, whether or not anyone remembered to check those other repos before merging. An agent working repo-by-repo, which is how most agents work by default, has no visibility into this. It needs the library repo and every consumer repo indexed into the same graph, with edges that cross the repository boundary the way real dependencies do. That's a structural requirement: no instruction in a prompt gives an agent visibility into repos it was never shown.

How agents guess today

Absent a real blast radius answer, agents fall back on a mix of grep and pattern recognition: search for the symbol name, read a few of the matching files, infer the rest from naming conventions and folder structure. It works often enough on small, well-organized codebases to build false confidence, and it fails quietly on the codebases where it matters most: large, inconsistently named, with interfaces and dependency injection in the mix. The agent ships the edit and reports it as done, confident by default because nothing in its process flagged uncertainty.

Blast radius as an MCP call

This is what a graph makes callable. Symvanta exposes a relate tool over MCP with kind: "blast_radius": pass a selector for the symbol you're about to change and get back the callers, the transitive dependents, and, where the repositories are linked, the cross-repo callers too, resolved from parsed calls and implementations. The same relate tool answers narrower questions as well: direct callers, dependencies, interface implementers, so an agent can start with a cheap direct-callers check and only pay for the full blast radius when the change actually warrants it. Selectors accept a nodeId or a plain symbol name, and the response returns file paths and line bounds an agent can hand straight to its editor. This is one instance of the broader pattern we cover in why AI coding agents fail on large codebases: a graph turns a connectivity question into a lookup.

Embeddings alone can't answer this kind of question, because similarity search ranks how much two snippets resemble each other, a different signal than which symbols call which; we go into that distinction directly in code embeddings vs code graph. If you're evaluating this space more broadly, see how this compares to Augment Code, another tool agents reach for when navigating unfamiliar code.

Honest limits

A parsed call graph sees what's written in the source. It does not reliably see everything that happens at runtime. Dynamic dispatch through a container that resolves an implementation by string key at boot time, reflection that invokes a method by name computed from a config value, plugins loaded from a directory scan, these are real edges in the running system that a static graph can miss or represent only partially. The honest fix for that class of problem is runtime signal, traces and logs, merged with the static graph. A blast radius tool worth trusting gets the static case right, ordinary calls and interfaces with no false negatives, and states plainly which dynamic cases it can't fully resolve.

Want to see blast radius run against your own repository, with your own callers and your own interfaces? Book a 15-minute demo.

See Symvanta on your own codebase →