DEV Community

Blake Yang
Blake Yang

Posted on

Trace Callers Before the Diff: A Blast-Radius Card for Shared OSS Helpers

The following labeled scenario opens this walkthrough and does not name a public repository or vendor team. A contributor proposed a timeout fix against a shared helper that twelve command modules imported through thin wrappers. The unit file beside that helper stayed green, so the change looked complete during a fast skim. A later release candidate failed because a worker still called the previous signature through another package.

Maintainers then rebuilt the call graph by hand, which delayed the tag and exhausted scarce review time. The missing artifact was not extra commentary pasted into the pull request description or chat thread. Reviewers needed a caller inventory that anyone could replay from the branch without extra tribal knowledge.

Why a green neighbor test is not blast-radius proof

File-local tests answer a narrow question about the hunk under the cursor and its closest fixtures. Shared helpers in open source libraries usually sit behind several import paths, generated shims, and optional extras. A green test in the same directory does not prove those paths still compile, still type-check, or still honor timeouts.

Vibe-shaped patches make this gap worse because the author narrates intent instead of listing dependents. Engineering a contribution means publishing the blast radius before the diff, then refusing hunks that fall outside that card. The rest of this article is a replayable workflow for that card, including harvest commands, a contract table, and a frozen review prompt.

The blast-radius card maintainers can replay

Keep one markdown file in the branch, named BLAST_RADIUS.md, and treat it as a merge gate rather than decoration. The card must be complete enough that a reviewer who never joined the issue thread can still reconstruct the risk. Label the following template as a proposal that should be filled from the working tree, not from memory.

# Blast-radius card

- Issue: #
- Symbol(s) under change:
- Public signature before:
- Public signature after:
- Intended behavior change (one sentence):
- Forbidden behavior change:
- Call sites harvested (path:line):
- Call sites excluded, with reason:
- Tests that must change:
- Tests that must stay green without edits:
- New dependency allowed: yes/no
- Rollback note:
Enter fullscreen mode Exit fullscreen mode

Three rules keep the card honest during review. Every listed call site must come from a command that a stranger can rerun. Every excluded path must carry a reason that cites an import boundary, a generated file, or an unused extra. Every intended behavior change must fit one sentence, because a second sentence usually hides a second patch.

Harvest call sites from the tree, not from chat

Start from a clean branch that tracks the issue and avoids mixed chores. Record the symbol names before editing, including aliases and re-exports that wrappers may use. The commands below are labeled examples for a Python-like tree; adapt the patterns to the language under review.

git status --short
git rev-parse --abbrev-ref HEAD

# Example harvest for a helper named apply_timeout
rg -n --hidden -g '!vendor/**' -g '!dist/**' \
  'apply_timeout|from \.timing import apply_timeout|timing\.apply_timeout'

# Confirm the definition sites before classifying callers
rg -n '^def apply_timeout|^async def apply_timeout' 
Enter fullscreen mode Exit fullscreen mode

Paste every hit into the card with path and line, then classify each hit as a direct caller, a re-export, a test double, or noise. Delete noise only when the file cannot import the symbol at runtime, and write that reason beside the exclusion. A second pass with git grep catches generated files that some ignore rules hide from rg.

git grep -n 'apply_timeout'
git log -S 'apply_timeout' --oneline -- max-count=20
Enter fullscreen mode Exit fullscreen mode

The history search is not a substitute for the inventory. It only shows whether the symbol recently moved, which often explains stale wrappers that tests never import. When the log shows a rename, add the old name to the harvest patterns and extend the card before any production hunk is written.

A small classifier script for the harvest

The following Python sketch is an unexecuted example that turns ripgrep-style lines into a table. Run it only after redirecting harvest output to a text file, and treat its labels as hints rather than proof.

# unlabeled proposal: classify harvested lines into a blast-radius table
from pathlib import Path

KINDS = (
    ("test", ("/tests/", "_test.py", "test_")),
    ("reexport", ("__init__.py",)),
    ("wrapper", ("compat.py", "shims/")),
)

def classify(path: str) -> str:
    for kind, needles in KINDS:
        if any(n in path for n in needles):
            return kind
    return "caller"

lines = Path("harvest.txt").read_text().splitlines()
for raw in lines:
    if ":" not in raw:
        continue
    path, rest = raw.split(":", 1)
    print(f"{classify(path):9} {path}:{rest}")
Enter fullscreen mode Exit fullscreen mode

Commit the harvest file beside BLAST_RADIUS.md so reviewers can rerun classification without trusting a screenshot. If the classifier and the card disagree, stop and repair the card before writing product code. Disagreement at this stage is cheaper than a revert after a release candidate.

Freeze a contract table before the first production hunk

Turn the intended change into a table that later tests can fail against. Columns stay boring on purpose: input, old observable, new observable, and owners of that path. Rows that cannot name an owner do not belong in the patch.

Call site Input that matters Old observable New observable Owner test
cmd/sync.py:88 HTTP client timeout 30s default 5s default tests/cmd/test_sync.py
worker/retry.py:41 retry wrapper timeout inherits helper must stay 30s tests/worker/test_retry.py
compat/old_api.py:12 re-export same signature same signature tests/compat/test_export.py

The middle row is the reason this workflow exists. Shared helpers often have one caller that must change and another caller that must not change, and file-local tests cannot see the second caller. If the table needs more than five rows, split the work into two issues rather than smuggling a migration through a timeout fix.

Patch against the card, then move the tests that own the rows

Edit production code only after the card and table are committed. Keep the diff inside the listed symbols, and reject drive-by formatting in files that are not call sites. When a wrapper needs a new argument, add it as an explicit keyword with a default that preserves the forbidden behavior change.

# labeled example: preserve the worker path while tightening the CLI path
def apply_timeout(client, seconds=30, *, inherit=True):
    if inherit:
        return client
    client.timeout = seconds
    return client
Enter fullscreen mode Exit fullscreen mode

Run the owner tests named in the table before the full suite, because those tests are the contract. A patch that leaves every owner test untouched has not demonstrated the new observable. A patch that rewrites unrelated assertions has expanded the blast radius after the card was frozen, which should fail review.

pytest -q tests/cmd/test_sync.py tests/worker/test_retry.py tests/compat/test_export.py
pytest -q
git diff --stat
Enter fullscreen mode Exit fullscreen mode

--stat is part of the gate. If the file list contains modules absent from the card, restore those files or amend the card with a new harvest. Reviewers should not have to discover extra files by scrolling a surprise diff.

Frozen-prompt review after the card is complete

After the blast-radius card, the contract table, and the tests exist, a second pass can interrogate those files without extra narrative. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Contributors without a spare checkout can keep that frozen packet on MonkeyCode's free server option and run the same prompt through free model access, so the model sees only the card, the harvest, and the diff.

The prompt below is a labeled template. Paste the files; do not paste issue chatter, because chatter reintroduces assumptions the card was meant to kill.

You are reviewing an OSS patch packet, not writing new code.
Read BLAST_RADIUS.md, harvest.txt, the contract table, and the diff.
Report only:
1) call sites in the diff that the card does not list
2) call sites on the card that the diff does not touch, with risk
3) owner tests that did not move for a changed observable
4) signature changes that violate the forbidden behavior line
Refuse to suggest extra features. If the packet is incomplete, say incomplete.
Enter fullscreen mode Exit fullscreen mode

Treat the model output as a checklist against the working tree, not as a merge vote. Every finding must be confirmed with rg or a failing test before the author changes code. Findings that cannot be replayed from the packet are discarded, because they are vibes wearing a reviewer's coat.

Decision table for this workflow

Situation Action Merge stance
Harvest and card disagree Stop and repair the card Block
Owner test did not move Add or fail the observable Block
Diff file absent from the card Restore or re-harvest Block
Model flags a missing caller, rg confirms Extend tests, then patch Block until green
Model flags a missing caller, rg is clean Discard the finding Continue
Typo in a comment only Skip the card Allow
Security embargo or private fixture Do not upload the packet Use a private review path

The table is the engineering part of the loop. Tools can harvest and nag, but they cannot decide which callers are load-bearing. Humans still own that classification, and the card makes the classification visible.

Limitations and who should skip this card

This workflow assumes the repository has searchable call sites and tests that can name an observable. Generated parsers, huge monorepos without import conventions, and binary plugins will produce noisy harvests that look complete while missing real callers. In those trees, the card still helps, but it cannot be the only gate.

Authors should skip the full card for single-line comment fixes, changelog-only pull requests, and lockfile refreshes that do not touch runtime symbols. They should also skip uploading a packet that contains secrets, private customer fixtures, or embargoed vulnerability detail to any hosted model or shared server. Maintainers should reject the method when a contributor uses the model output as a substitute for running the owner tests.

The approach also fails when the intended change cannot fit one sentence on the card. That failure is useful, because it usually means the issue is a migration and needs a staged plan. Splitting the work is slower on the first day and cheaper than a revert after a green-looking helper change lands.

A blast-radius card does not make a patch small, and it does not make a model authoritative. It makes the dependents visible before the diff, which is the part most vibe-shaped OSS reviews still skip. Contributors who already freeze that card can run the frozen review pass with free model access when another local checkout is the wrong place for the packet.

Top comments (0)