Fetch the PR diff, parse changed functions, filter generic noise names, then run ripgrep locally to trace every call site. A producer-consumer table generated in seconds — no SSH, no staging environment required. Here's the implementation.
The most common source of “looked right in isolation, broke a caller” regressions is shipping a change whose consumer impact was never measured.
You change a function. The change is correct for the ticket you’re fixing. You don’t know that 14 other files call that function, three of them with assumptions your change invalidates. The PR gets merged. Something breaks in production that has nothing to do with the reported bug.
Blast radius analysis is the practice of mapping the consumer impact of every change before the PR is created.
ripgrep search to find every file that references itAll of this runs locally. No SSH. No staging environment. No waiting for CI.
The diff is unified diff format. Changed symbols appear on lines starting with + (added) or - (removed). For PHP, the patterns are:
function symbolName(
class ClassName
interface InterfaceName
const CONSTANT_NAME
For TypeScript/JavaScript:
function symbolName(
export function symbolName(
const symbolName =
class ClassName
A simple regex pass over the diff lines extracts candidate names. The important part is extracting name only — not the full signature — so the ripgrep search can find all call sites regardless of how the function is invoked.
Some function names are so generic that ripgrep returns hundreds of matches, most of them unrelated to the function you actually changed. These flood the output and make it useless.
Maintain a blocklist of noise names:
const NOISE_NAMES = new Set([
'execute', 'render', 'run', 'get', 'set', 'init', 'index', 'handle',
'create', 'update', 'delete', 'find', 'save', 'load', 'process',
'__construct', '__destruct', '__toString', '__get', '__set', '__call',
]);
Any symbol in this set is skipped. The analysis only runs on symbols specific enough to have meaningful ripgrep results.
For each non-noise symbol, run ripgrep against the codebase:
const result = execSync(
`rg --json -l "${symbol}" ${repoPath} --include="*.php"`,
{ maxBuffer: 10 * 1024 * 1024 }
);
The -l flag returns only file paths (not line content), keeping the output manageable. For each file found, run a second pass with line numbers to get context:
const lines = execSync(
`rg -n "${symbol}" ${file}`,
{ maxBuffer: 1024 * 1024 }
);
The result is a list of { file, line, context } entries for each symbol — every place in the codebase that references the changed code.
Not all consumer counts are equal. Apply risk tiers:
The block threshold exists because high consumer count combined with low confidence in the fix is the exact signature of “we might be about to break something important.” A human needs to review this before it merges.
The blast radius summary becomes part of the PR description:
## Blast Radius
**Changed:** `calculateInvoiceTotal()`
**Consumers:** 8 files
Top consumers:
- `InvoiceController.php:142` — direct call in POST handler
- `InvoiceReport.php:89` — used in monthly summary export
- `SubscriptionRenewal.php:201` — called during billing cycle
- `InvoiceTest.php:44` — test coverage exists ✓
This makes the impact visible to human reviewers without requiring them to run their own analysis. The information that would have been discovered in a post-merge incident is now surfaced before the merge decision.
Blast radius analysis takes 2–5 seconds for a typical codebase. It requires no infrastructure beyond a local checkout and ripgrep. The cost of running it on every PR is essentially zero.
The cost of not running it is a production regression that takes hours to diagnose because the connection between the change and the breakage isn’t obvious.
Build it as a mandatory pre-PR step. Make skipping it an explicit choice that has to be justified, not the default.