DEV Community

Jordan Li
Jordan Li

Posted on

Triage an Agent PR: Trust, Revert, or Test Each Hunk

A backend team received an agent-generated pull request on Tuesday morning. The subject line claimed a quiet session-cookie expiry fix. A green CI badge sat under a 240-line TypeScript diff.

The human reviewer had eighteen minutes before standup. The agent summary sounded complete, confident, and specific. That mix is how silent regressions reach production traffic.

This article walks through a hunk-level triage method. Each change is marked trust, revert, or test. The workflow stays useful without any hosted model.

The scene inside PR 418

The constructed example below is not a production incident. It mirrors patterns that show up in agent patches.

Auth middleware, cookie flags, and a retry helper collide. The agent also rewrote error handling for supposed clarity.

It added a shared sleep helper that already existed. It relaxed a cookie Secure flag inside a test fixture. It then copied that fixture into the main runtime path.

Those three moves look unrelated in GitHub's file view. They form one failure mode during a careful review. The agent optimized for a passing demo, not the contract.

What agent PRs hide from reviewers

Agent patches often compile, format, and lint without noise. A tidy diff can still change a public contract.

Cheap agent patches raise review load, not design quality. Technical debt arrives as extra helpers and quiet defaults.

Common failure classes appear together in one branch. The numbered list below is a short memory aid.

  1. Scope creep appears beside the stated bug fix.
  2. Duplicate helpers that drift from existing utilities.
  3. Swallowed errors wrapped in silent retry loops.
  4. Test fixtures leaking into runtime cookie paths.
  5. Comments that describe code the model never wrote.

None of these patterns require malice from the model. They require a slower pass than the agent used.

The three-bin review

Every hunk goes into exactly one review bin. Mixed hunks should be split before any merge.

Trust

The change is local, named, and already covered. Types match the surrounding module without new I/O. No new network calls and no new default values.

Revert

The change is unexplained, broad, or security-adjacent. Public signatures, cookie flags, and auth paths start here. Timeouts and retry wrappers belong here until proven.

Test

The change might be correct and remains observable. Reviewers write or extend a test before keeping it.

The three bins are ordered on purpose for safety. Reviewers revert first, then test, then trust leftovers.

Decision table

The table is only a starting filter for tired reviewers. It does not replace reading the full unified diff.

Signal in the hunk Bin First action
Cookie, CORS, CSRF, or auth header Revert Restore prior flags
New retry, timeout, or sleep Revert Drop unless a test names it
Duplicate util already in the repo Revert Call the existing helper
Pure rename with identical types Trust Keep if grep is clean
New branch in an existing function Test Add a focused unit test
Snapshot-only test update Revert Demand a behavior assertion
Lockfile or CI config churn Revert Cut it from this PR
Error swallow via any or empty catch Revert Restore typed failures

Apply the table before arguing about style. Style is not the merge risk here.

Numbered triage on a local checkout

Reviewers should work from a local git checkout. Browser-only review hides rename noise and binary hunks.

  1. Fetch the review branch and ignore the agent summary.
  2. List changed files, then open tests before production code.
  3. Walk the diff in hunks, not in whole-file mode.
  4. Mark each hunk as trust, revert, or test.
  5. Revert every revert-bin hunk before running the suite.
  6. Write focused tests for every remaining test-bin hunk.
  7. Keep trust-bin hunks only after those tests pass.

Commands for that checkout follow the seven steps. Paste them into a normal developer shell.

git fetch origin pull/418/head:review-418
git switch review-418
git diff main...HEAD --stat
git diff main...HEAD -- test src
git log --oneline main..HEAD
Enter fullscreen mode Exit fullscreen mode

Read the test diff before any production file. Agents often rewrite snapshots to match a new bug.

git diff main...HEAD -- '*.test.ts' '*.spec.ts' '__snapshots__'
rg -n "toMatchSnapshot|secure:\s*false|catch \(" src test
Enter fullscreen mode Exit fullscreen mode

Constructed production snippet

The agent touched cookie flags and error handling together. The TypeScript below is a labeled example, not shipped code.

// example only — agent-generated middleware fragment
export function attachSession(req: Request, res: Response, next: NextFunction) {
  const raw = req.headers.cookie ?? "";
  const token = parseSession(raw);

  retry(
    async () => {
      try {
        await hydrateUser(token);
      } catch (err) {
        return null; // swallow
      }
    },
    { times: 3, waitMs: 200 }
  ).catch(() => next());

  res.cookie("sid", token, {
    httpOnly: true,
    secure: false, // copied from a test fixture
    sameSite: "lax",
  });
}
Enter fullscreen mode Exit fullscreen mode

Three revert signals sit inside those twenty lines. Swallowed errors hide authentication failure from operators.

A retry turns a 401 response into extra latency. A false Secure flag is a production defect immediately.

A reviewer should not negotiate those three hunks. Revert them in one commit against the main branch.

git checkout main -- src/auth/session.ts
git commit -m "revert: restore session middleware from main"
Enter fullscreen mode Exit fullscreen mode

Keep a legitimate parser fix in a separate file only. Do not re-introduce the retry wrapper during that split.

What to test after the revert

The remaining change, if any, needs a behavior test. Do not accept a snapshot update as proof of correctness. Assert status codes and cookie attributes in plain code.

// example only — review-added tests, not executed here
import { attachSession } from "../src/auth/session";

test("sets Secure cookies on https", () => {
  const res = mockResponse();
  attachSession(mockRequest({ secure: true }), res, () => {});
  const cookie = String(res.headers["set-cookie"]);
  expect(cookie).toMatch(/Secure/i);
  expect(cookie).toMatch(/HttpOnly/i);
});

test("does not retry on 401 from hydrateUser", async () => {
  hydrateUser.mockRejectedValueOnce({ status: 401 });
  const next = jest.fn();
  await attachSession(mockRequest(), mockResponse(), next);
  expect(hydrateUser).toHaveBeenCalledTimes(1);
});
Enter fullscreen mode Exit fullscreen mode

If the agent cannot satisfy those tests, reject the PR. Close it and file a smaller, single-purpose task instead.

A hunk classifier you can run locally

The script below is a mechanical aid for tired eyes. It does not approve code and does not merge branches. It prints a suggested bin from path and diff keywords.

Save it as scripts/triage-diff.mjs in the repo. Run it against a unified diff from git.

#!/usr/bin/env node
import { createInterface } from "node:readline";

const REVERT_PATH = /(auth|session|cookie|csrf|cors|oauth|jwt|password)/i;
const REVERT_HUNK =
  /\b(secure\s*:\s*false|sameSite\s*:\s*['"]none|catch\s*\([^)]*\)\s*\{\s*return null|retries?\s*:|setTimeout|sleep\(|\bany\b|@ts-ignore)/i;
const TEST_HUNK = /\b(if\s*\(|switch\s*\(|\bnew |TODO|FIXME)\b/;

const files = [];
let current = null;

const rl = createInterface({ input: process.stdin });
for await (const line of rl) {
  if (line.startsWith("diff --git")) {
    if (current) files.push(current);
    current = { file: line, path: "", bin: "trust", notes: [] };
    continue;
  }
  if (!current) continue;
  if (line.startsWith("+++ b/")) current.path = line.slice(6);
  if (REVERT_PATH.test(current.path) || REVERT_HUNK.test(line)) {
    current.bin = "revert";
    current.notes.push(line.slice(0, 120));
  } else if (current.bin !== "revert" && TEST_HUNK.test(line)) {
    current.bin = "test";
  }
}
if (current) files.push(current);

for (const f of files) {
  console.log(`${f.bin.toUpperCase()}\t${f.path || f.file}`);
  for (const n of f.notes.slice(0, 3)) console.log(`  ${n}`);
}
Enter fullscreen mode Exit fullscreen mode

Wire a shell alias so the classifier stays in the review path. Keep the output next to the decision table.

chmod +x scripts/triage-diff.mjs
git diff main...HEAD | node scripts/triage-diff.mjs
Enter fullscreen mode Exit fullscreen mode

Treat every TRUST line as a hypothesis for humans. Humans still read authentication files line by line.

Optional second pass on a free server

A hosted model can draft the same bins from a redacted diff. It must never merge, approve, or rewrite git history.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option.

A reviewer pastes a redacted unified diff on that server. The request asks for a trust, revert, and test table. The output is a checklist, never a merge verdict.

Strip secrets before any paste leaves the laptop. Do not send production cookies, tokens, or customer records.

A short prompt shape follows for that optional pass. It is a proposal, not a measured benchmark.

Classify each hunk in this unified diff.
Bins: TRUST, REVERT, TEST.
Rules: auth, cookies, retries, swallowed errors, lockfiles -> REVERT.
New branches and pure logic -> TEST.
Renames with identical types -> TRUST.
Return a markdown table: path, bin, reason, test to add.
Do not praise the patch. Do not invent files.
Enter fullscreen mode Exit fullscreen mode

Compare the model table with the local classifier output. Conflicts go to revert without further debate. Agreement still needs a human read on security paths.

This second pass stays optional for every team. The local script and decision table already finish the review.

Limitations

The classifier uses keywords and simple path checks. It will miss clever breakage in plain helpers. It will flag docs that merely mention cookies.

The method assumes a test runner already exists. A repo with no tests cannot use the test bin. Those teams should revert agent hunks by default.

Do not use this workflow to auto-merge agent PRs. Do not use it on regulated code without a named human owner. Do not send a hosted model any diff that still contains secrets.

The free server and free model access are convenience layers. They do not replace CI, threat review, or on-call judgment. This article does not claim quotas, model names, or hardware.

Who should skip this

Skip the model pass when the diff includes credentials. Skip the whole method when the team cannot revert freely.

Skip it for binary assets, generated protobufs, and huge lockfile-only PRs. Those reviews need different tools and owners.

Staff engineers who already review auth by hand may take only the decision table. That smaller slice still changes the merge order.

Close

Agent PRs fail in clusters, not in single lines. Revert the unsafe cluster first, then test what remains. Trust only the leftover after those two bins.

The eighteen-minute review becomes a fixed sequence. Fetch, classify, revert, test, then read. The agent can wait for that sequence.

Teams on a small machine can add a free hosted model pass. The merge button stays under human control either way.

Top comments (0)