Plant the Bug Before You Trust the Reviewer: A Recall Audit for Free-Model PR Reviews
An example scenario, stated as a scenario Example scenario (illustrative; not a measured case, and not drawn from a named project): a maintainer of a small library has five pull requests queued and access to a free mod
An example scenario, stated as a scenario
Example scenario (illustrative; not a measured case, and not drawn from a named project): a maintainer of a small library has five pull requests queued and access to a free model endpoint that will review diffs. On the first pull request the endpoint returns six comments; three read plausibly, one references a function that does not exist in the patched tree, and none explains what a complete review would have covered. Nothing in that run defines ground truth, so the maintainer cannot say whether the reviewer is useful or merely fluent.
Public bug-fix datasets solve this problem for their own repositories, not for yours, because they differ in language, defect mix and review conventions. A better-labelled corpus is usually sitting in the repository's own history, since every merged fix is a commit where the defect and its repair are both recorded. Reversing such a commit produces a patch that looks like an ordinary cleanup and quietly reintroduces a real bug.
The workflow below turns that idea into a small harness: build the corpus, freeze the prompt, score file and symbol recall, then decide what authority the reviewer has earned. Every script here is labelled illustrative because nothing was executed while writing this article; the commands are ordinary Git usage, but the numbers are yours to produce.
Step 1 β Build a corpus from your own merged fixes
The corpus builder walks recent fix commits and derives two artifacts per commit: a reverse patch that reintroduces the defect, and a ground-truth record of the files and removed identifiers that the fix touched. Adapt the commit filter to your project's conventions, since ^fix matches only commits that actually start with that word.
#!/usr/bin/env bash
# build_corpus.sh β derive review tasks from merged fixes (illustrative)
set -euo pipefail
REPO="${1:?usage: build_corpus.sh <repo-path> [count]}"
N="${2:-15}"
OUT="$REPO/.review-audit"
mkdir -p "$OUT/diffs" "$OUT/truth"
git -C "$REPO" log --no-merges --grep='^fix' --format='%H' -n "$N" |
while read -r fix; do
parent="$(git -C "$REPO" rev-parse "${fix}^")"
# fixed tree -> buggy parent: applying this patch removes the fix
git -C "$REPO" diff --unified=3 "$fix" "$parent" > "$OUT/diffs/$fix.patch"
# ground truth: touched paths
git -C "$REPO" diff --name-only "$parent" "$fix" > "$OUT/truth/$fix.files"
# ground truth: identifiers the fix removed (they come back in the reverse patch)
git -C "$REPO" diff --unified=0 "$parent" "$fix" |
grep -E '^-' | grep -v '^---' |
grep -oE '[A-Za-z_][A-Za-z0-9_]{3,}' | sort -u > "$OUT/truth/$fix.symbols"
done
ls "$OUT/diffs" | wc -l
The argument order in git diff "$fix" "$parent" is deliberate, because the diff reads as a change that takes the current tree backwards. When that patch is later presented to a reviewer, it must be described exactly like any other proposed change, with no hint that a defect is present. Line numbers are deliberately excluded from ground truth here, since the reverse patch's line numbers refer to the buggy tree and a naive comparison would score noise.
Validity gate: keep only defects with an observable failure
A reverse patch that no test detects is weak ground truth, because the audit then measures a judgement call rather than a known break. Apply each reverse patch in a scratch worktree, run the project's test command, and record the exit status next to the corpus entry.
OUT="$REPO/.review-audit"
for fix in $(ls "$OUT/diffs" | sed 's/\.patch$//'); do
git -C "$REPO" worktree add --detach "/tmp/audit-wt-$fix" "$fix" >/dev/null 2>&1 || continue
if git -C "/tmp/audit-wt-$fix" apply "$OUT/diffs/$fix.patch"; then
( cd "/tmp/audit-wt-$fix" && ./run-tests.sh ) ; echo "$?" > "$OUT/truth/$fix.observed"
else
echo "apply_failed" > "$OUT/truth/$fix.observed"
fi
git -C "$REPO" worktree remove --force "/tmp/audit-wt-$fix"
done
grep -L '^0$' "$OUT"/truth/*.observed
Entries whose observed file is 0 describe a bug your suite never caught, which is a legitimate finding but a poor recall target. Drop entries with apply_failed, and drop lockfile-only, formatting-only and vendored-code fixes, because they add review surface without adding defect signal.
Step 2 β Freeze the prompt and the output schema
Repeatability depends on an invariant request, so the prompt must be identical across models, repeats and days, and it must never reveal that a defect exists. The schema below asks for file:line claims because they make scoring mechanical, and it adds an explicit silence token so a reviewer that always writes prose can be told apart from one that always finds something.
You are reviewing a proposed change to a <language> repository.
Report only issues you can locate in the changed lines.
For each issue, emit exactly one line:
<path>:<line> <low|med|high> <one-sentence claim>
If you find no issue, emit exactly: NO_ISSUES
No preamble, no summary, no praise.
Two operational details decide whether the run means anything. First, log the model string you requested plus any model identifier the response carries, because free endpoints may route requests to different models over time. Second, keep temperature and any sampling parameter fixed in the request, and record them in the run log next to the model string.
Step 3 β Score file recall, symbol recall and silence rate
The scorer below reads a JSONL run log with keys task, model, repeat and text, and prints three numbers per model. File recall is the primary metric; symbol recall is a weaker tiebreaker, because removed identifiers are matched by substring and common words inflate it.
#!/usr/bin/env python3
"""score_audit.py β illustrative; not executed for this article."""
import collections, json, pathlib, sys
truth = pathlib.Path(sys.argv[1]) # .../.review-audit/truth
runs = pathlib.Path(sys.argv[2]) # runs.jsonl
def hit(text, needles):
low = text.lower()
return any(n.lower() in low for n in needles)
scores = collections.defaultdict(lambda: {"n": 0, "file": 0, "sym": 0, "silent": 0})
for line in runs.read_text().splitlines():
row = json.loads(line)
files = (truth / f"{row['task']}.files").read_text().split()
syms = [s for s in (truth / f"{row['task']}.symbols").read_text().split("\n") if s]
s = scores[row["model"]]
s["n"] += 1
s["file"] += hit(row["text"], [f.split("/")[-1] for f in files])
s["sym"] += bool(syms) and hit(row["text"], syms)
s["silent"] += "NO_ISSUES" in row["text"]
for model, s in sorted(scores.items()):
n = max(s["n"], 1)
print(f"{model}: n={n} file_recall={s['file']/n:.2f} "
f"symbol_recall={s['sym']/n:.2f} silent_rate={s['silent']/n:.2f}")
Hand-inspect at least five scored runs before trusting any aggregate, because a reviewer that lists every file in the repository will score near-perfect recall and remain useless. The shape of a result table is what matters, not any number reproduced here: two models, three repeats, fifteen tasks, and per-model rows for file recall, symbol recall and silence rate. Post-process those rows into one labelled decision and stop there, since below roughly ten corpus entries the confidence interval swamps the difference between models.
Step 4 β Decide what authority the reviewer has earned
The table converts a measurement into an allowed use, and the forbidden column matters more than the permitted one. A reviewer with high recall and high noise is not a cheap human replacement; it is an unpriced interruption.
| Observed on your corpus | Permitted use | Explicitly not permitted |
|---|---|---|
| file recall below 0.3 | none; read the output as casual opinion only | gating a pull request, claiming coverage |
| recall 0.3β0.6, silence rate above 0.5 | triage: rank pull requests by distinct flagged files | any statement that a patch was reviewed |
| recall above 0.6, silence rate below 0.3 | second-pass attention list before a human reads the diff | approval, merge rights, security sign-off |
| any recall with flags on most files | nothing until the prompt is tightened | cost-savings estimates from reduced review time |
Track at least one defect class separately when the corpus supports it, because aggregate recall hides the classes a reviewer never notices. Null handling, off-by-one boundaries and resource cleanup fail in different ways, and a model strong on one class is routinely blind to another.
Step 5 β Where free model access and a free server change the arithmetic
One maintainer running fifteen tasks across three models and three repeats issues 135 review calls, which is too many to absorb as a recurring personal expense for a hobby repository. Free model access removes that budget negotiation, which matters precisely because the audit must happen before any spending decision rather than after it.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is the project whose operator described free model access and a free server option for this workflow, and, as stated at the time of writing, a free token allowance on the order of ten million tokens. Allowances and model availability change without notice, so confirm the current terms on the project's own pages before planning a corpus size around them. The harness itself is I/O-bound and mostly idle, so scheduling it as a batch run on a free server leaves the local machine for builds, tests and the debugger, which is the resource the audit competes with most.
Limitations, and who should skip this
- Corpus size dominates everything. Fewer than ten validated entries produce recall figures too unstable to compare across models, so report the count beside every percentage.
- A reversed fix is a synthetic regression, and reviewers may behave differently on a live pull request that carries discussion, CI status and reviewer history.
- The audit measures detection of defect classes already present in your history, and it says nothing about novel classes, design flaws or security properties.
- Repositories where fixes arrive as squashed upstream merges, dependency bumps or generated code will spend most of the harness on noise rather than on review signal.
- Free-tier terms, routing and model identity can change silently, so an audit is a snapshot with an expiry date rather than a certification; re-run it when the model string changes.
- Skip the approach entirely if your queue is small, your test suite is thin, or policy already requires line-by-line human sign-off, because no figure from this harness should ever grant approval authority.
A minimal cadence
Re-run the audit after any prompt edit, whenever the endpoint's model string changes, and once per quarter as a baseline check. Keep the corpus, the run log and the scorer under a path excluded from packaging, so the comparison is reproducible rather than remembered. Maintainers who want to test the shape of this on real history can start with the corpus builder and one model, then decide whether the free tier is worth wiring into a scheduled job.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.