DEV Community

Casey Chen
Casey Chen

Posted on

The Diff Is Lying: A 15-Minute Agent PR Postmortem That Runs on Free Tooling

Agent-generated pull requests rarely fail because of syntax errors. They fail in the silent gaps between the lines — swallowed exceptions, off-by-one date boundaries, copy-pasted error handlers, dead branches that only trigger on Tuesdays. CI won't catch those. A code review should.

This post gives you a scripted 15-minute postmortem that turns a diff into a list of suspicious lines, then verifies each one against a clean execution environment. It uses free model access and a free server option from MonkeyCode for the heavy lifting, but the procedure itself is tool-agnostic.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

Why a normal review fails on agent code

When a human writes a PR, they carry context from the codebase conversation. An agent carries a context window. It has seen the relevant files, but it has not lived through the bug reports, the logout edge case, the timezone migration. So its code inherits the agent's confidence and the agent's amnesia.

That leads to three recurring defects:

  • Optimistic path only — the happy path works, error paths are stubbed with pass or a generic raise.
  • Surface-level consistency — variable names match the style guide, but the logic ignores adjacent invariants.
  • Self-fulfilling tests — tests assert what the implementation does, not what the spec requires.

A diff review that reads top-to-bottom cannot see these because they live in the interactions with existing code. You need structured questions.

The artifact: pr_autopsy.py

Here's a small script that scans a unified diff and flags the patterns above. It's pure Python, no dependencies:

#!/usr/bin/env python3
"""pr_autopsy.py - scan a unified diff for common agent-generated PR symptoms."""
import re
import sys
from collections import defaultdict

SUSPECT_PATTERNS = {
    "bare_except": re.compile(r"^\+\s*except\s*:$", re.MULTILINE),
    "pass_on_error": re.compile(r"^\+\s*pass\s*$", re.MULTILINE),
    "todo_comment": re.compile(r"^\+\s*#\s*(TODO|FIXME|HACK)", re.MULTILINE),
    "magic_number": re.compile(r"^\+\s*.*[=<>]+\s*\d{3,}"),
    "time_hardcoded": re.compile(r"^\+\s*.*(?:sleep|timeout|expire).*=\s*\d+"),
    "dup_error_handler": re.compile(r"^\+\s*\w+\s*=\s*(\w+)\s*\n(?:^\+\s*\w+\s*=\s*\1\s*\n)+"),
}

def parse_diff(diff_text):
    current_file, current_line = None, 0
    for line in diff_text.splitlines():
        if line.startswith("+++"):
            current_file = line[6:].strip()
            current_line = 0
        elif line.startswith("@@"):
            m = re.search(r"\+(\d+)", line)
            current_line = int(m.group(1)) if m else 0
        elif line.startswith("+") and not line.startswith("+++"):
            yield current_file, current_line, line
            current_line += 1
        elif line.startswith("-") and not line.startswith("---"):
            continue
        else:
            current_line += 1

def main(diff_path):
    with open(diff_path, encoding="utf-8") as f:
        diff = f.read()
    findings = defaultdict(list)
    for fname, lineno, line in parse_diff(diff):
        for name, pattern in SUSPECT_PATTERNS.items():
            if pattern.search(line):
                findings[fname].append((lineno, name, line.strip()[:80]))
    for fname, hits in findings.items():
        print(f"### {fname}")
        for lineno, name, snippet in hits:
            print(f"  {lineno}: [{name}] {snippet}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("usage: pr_autopsy.py <diff-file>")
        sys.exit(1)
    main(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

Run it against a saved diff:

git diff main...agent-branch > agent_pr.diff
python3 pr_autopsy.py agent_pr.diff
Enter fullscreen mode Exit fullscreen mode

It is intentionally shallow. The value isn't the matches — it's the trail they lead you to. Each flag is a question, not a sentence.

Applying it to a realistic agent PR

Here's a condensed set of changes from a synthetic agent PR that converts Unix timestamps to dates for a reporting dashboard:

+++ b/report_utils.py
@@ -42,7 +42,7 @@ def build_report(timestamps):
     for ts in timestamps:
-        d = datetime.utcfromtimestamp(ts)
+        d = datetime.fromtimestamp(ts)
         rows.append((d.year, d.month, d.day, compute_metrics(ts)))
Enter fullscreen mode Exit fullscreen mode
+++ b/report_utils.py
@@ -55,13 +55,17 @@ def build_report(timestamps):
             metrics = _cached_metrics[ts]
         except KeyError:
             metrics = compute_metrics(ts)
-            _cached_metrics[ts] = metrics
+            pass
     return rows
Enter fullscreen mode Exit fullscreen mode
+++ b/tests/test_report.py
@@ -12,7 +12,7 @@ def test_month_break():
     ts = date(2026, 2, 28).timestamp()
     result = build_report([ts])
-    assert result[0].year == 2026
-    assert result[0].month == 2
+    assert result[0].year == 2026
+    assert result[0].month == 2  # will this break on a leap year?
Enter fullscreen mode Exit fullscreen mode

Run the autopsy:

python3 pr_autopsy.py agent_pr.diff
Enter fullscreen mode Exit fullscreen mode

You'll see output like:

### report_utils.py
  45: [magic_number] datetime.fromtimestamp(ts)
  58: [pass_on_error] pass
### tests/test_report.py
  15: [todo_comment] # will this break on a leap year?
Enter fullscreen mode Exit fullscreen mode

None of these are fatal alone. Together they tell a story: the agent changed a timezone function, swallowed a cache write, and left a speculative comment in a test. That's exactly the combination that produces a production incident two weeks later.

The free-server verification loop

Now the trustworthy part: don't take the diff's word for it. Spin up a clean environment and exercise the suspicious paths. I used a free server from MonkeyCode for this sandbox — it boots in seconds and gives you a disposable shell, which is all you need.

The loop is three steps:

  1. Revert the suspicious line — temporarily restore the original code in the sandbox.
  2. Run the existing test suite — watch for tests that start failing or passing.
  3. Add a boundary test — for a timezone change, test midnight UTC vs local, a leap day, a timestamp before 1970.

Example boundary test for the timestamp change:

from datetime import datetime, timezone, timedelta

def test_timezone_aware():
    local_now = datetime(2026, 9, 2, 12, 0, tzinfo=timezone(timedelta(hours=2)))
    ts = local_now.timestamp()
    row = build_report([ts])[0]
    # previously this returned UTC, now it returns local
    assert row.hour == 12  # passes on the agent's machine, fails in UTC CI
Enter fullscreen mode Exit fullscreen mode

This test isn't in the agent's PR. That's the point.

What to trust, revert, and test

After the loop, classify every touched hunk with this decision table:

Condition Action
Diff matches a stated requirement and tests fail without it Trust — but require the agent's test, not the assertion rewrite.
Diff changes behavior outside the PR description Revert — or split into a separate PR with its own rationale.
Diff fixes a bug but adds no test Test — ask for a regression test before merging.
Diff introduces a pass or bare except Revert — these are never necessary in a non-prototype codebase.
Diff comment contains "maybe", "probably", or "?" Test — the comment is a confession of uncertainty.

The table doesn't remove human judgment. It replaces vibes with an audit trail.

Limitations and who shouldn't use this

This procedure is a triage, not a proof. pr_autopsy.py produces false positives on legitimate code; the patterns are heuristics, not logic errors. And the free server approach gives you a clean environment, not a full staging replica — don't use it for load testing, secrets, or anything that needs persistent state.

Skip this entire workflow if you're reviewing a 10-line dependency bump, or if you already have a formal verification pipeline with property-based tests. For those cases, the review is the diff — you don't need archaeology.

The one thing to take away

Agent PRs deserve the same treatment as any unfamiliar code: verify behavior in a clean environment, not your remembered context. A 15-minute scripted postmortem catches the silent defects that make you regret trusting automation. Run the script, spin up a free box, and decide with evidence.

If you want more concrete patterns like this, I have a longer checklist in a previous post about filtering agent PRs. But this script alone should pay for itself in the first week.

Top comments (0)