A PR review agent that posts 20 comments per diff is worse than useless. Two are real bugs. Four are true but irrelevant. Six are opinions dressed as findings. Five comment on code the PR never touched. Three are flat wrong. Every comment is beautifully formatted with severity labels and code snippets. The author now has to read 20 comments to find 2. Next PR, they skim. The one after that, they stop reading. Precision problem becomes a trust problem. A human staff engineer leaves 2 comments and approves. That's not laziness—that's the job.

This article walks through five rules I encoded in a review agent skill for klaussy-agents, an MIT-licensed CLI that scaffolds review skills into a repo. The prompt excerpts are real, from skills/review/SKILL.md and sub-agents.md.

1. Make Silence a Valid Outcome

A model asked to review code assumes it was asked to produce findings. Zero findings reads like failure, so it pads. The fix is to mark silence as a valid and wanted outcome:

> Precision over recall. Default to not reporting. If no finding is one a competent author would clearly want to fix, return an empty review and say so. An empty review is a valid, good outcome, not a failure. Do not invent findings or pad to look thorough.

"Be concise" gets interpreted as "write shorter comments"—you get 20 terse comments instead of 20 long ones. "An empty review is a valid, good outcome" changes the target, not the word count.

Pair this with a rule that makes padding expensive:

> Every finding must name a concrete trigger. State the specific input, state, or execution path that makes it go wrong. If you cannot describe how the problem is reached, you have not proven it—drop it.

Most padding cannot survive that. "Consider extracting this constant" has no input that triggers it. "This N+1 fires once per row in the results list, so a 500-row page makes 500 queries" does. Requiring the trigger is a filter the model applies to itself.

One banned practice: self-assigned confidence scores.

> Don't self-assign confidence scores. A number you make up is noise; the trigger path above is the real evidence.

"Confidence: 85%" is a number the model invented. It looks like calibration and isn't. Make the model produce evidence, not a probability.

2. Trace Every Path, Then Narrow Again

Focus is not shallowness. Dangerous bugs usually live one call up from the diff hunk.

Read whole files, never just hunks. The context phase reads the full text of every reviewable changed file in one parallel batch before any analysis.

Audit what was deleted. This catches more regressions than any other rule:

> Removed-behavior audit: for every deleted or replaced line in the diff, name the invariant, guard, or behavior it enforced, then confirm the new code re-establishes it (or that dropping it is intentional and safe). Silently removed checks are a top source of regressions.

Additions get scrutinized because they're new. Deletions get skimmed because a red line looks like cleanup. A null check that vanished during a refactor is invisible in review and obvious in production.

Recalibrate by creating a steelman. Every finding goes through this:

> Argue the author's side, then refute it. For each finding, write the strongest one-line case that it is not a real problem (the input can't occur, a caller already guards it, the framework handles it). Then either refute that case with specific code evidence, or drop the finding as a likely false positive.

Nothing else removes as many bad findings. Models are agreeable by default. Forcing an explicit steelman kills most false positives at the source. The validator reads callers and callees to do it.

3. Treat Context as a Budget

A large PR won't fit usefully in one window. Three moves:

Delete noise before the model sees it. Lockfiles, dist/, node_modules/, minified bundles, generated protobuf, pure renames. A deterministic pre-pass strips them—a job for code, not a model:

LOCKFILES = frozenset({"package-lock.json", "yarn.lock", "poetry.lock",
"uv.lock", "Cargo.lock", "go.sum", ...})
NOISE_DIR_PARTS = frozenset({"node_modules", "vendor", "dist", "build",
"out", ".next", "__generated__", ...})

A PR that reads as 4,000 changed lines is routinely 600 after trimming. The trimmed diff is followed by an "Excluded from review" manifest listing every dropped file and the reason—silent truncation reads as "I covered everything" when it didn't.

Split by lens, not by chunk. Above 150 changed lines, the review fans out to parallel sub-agents, each with the same diff and one job:

> You are a senior engineer reviewing a pull request. Your ONLY focus is the lens described below. Other concerns (correctness, architecture, security, scope, etc.) are handled by parallel reviewers—ignore them.

Lenses: correctness/concurrency, architecture/performance/reliability/dependencies, security/readability/tests, scope/conventions. Two more activate conditionally: agentic/evals for AI code, and architecture-decision for ADRs or design docs. Chunk-splitting means no agent ever sees the whole change—cross-file bugs go missing. Lens-splitting means every agent sees the whole change and is responsible for one kind of question, just like a good human review rotation.

Fan out the slow part too. Validation is the most expensive phase because every finding means reading files and tracing paths. Above six findings, validators run in parallel batches of four to six, grouped by file.

Cost lever: not every lens needs the same model. Scope and conventions runs on a cheap fast model; correctness, security, and architecture keep the expensive one.

4. The Repo's Conventions Outrank the Industry's

General-purpose review agents consistently annoy developers because the model knows what modern code looks like, but your repo is 11 years old. Those disagree, and the model is wrong to resolve in favor of the industry.

If the codebase uses callbacks throughout, a comment recommending async/await is not a review finding—it's a refactor proposal. Consistency with surrounding code is worth more than proximity to current best practice.

The conventions lens gets its standards from the repo:

> ### Project Conventions > {{REPO_SPECIFIC_CHECKS}} > If no repo-specific checks are listed above, read CLAUDE.md and any matching .claude/rules/*.md for the area being changed, and verify the PR adheres to the conventions and known pitfalls listed there.

{{REPO_SPECIFIC_CHECKS}} is filled in at scaffold time by parsing the repo's own CLAUDE.md and path-scoped rule files into check items. Convention findings must cite the rule they're breaking:

> For convention violations, reference the specific convention (file path or section in CLAUDE.md / .claude/rules/).

If the agent can't point at where a convention is written down, it has a preference—preferences don't go in review comments.

5. Thorough and Excessive Are Different Axes

Thoroughness and brevity are not a tradeoff. Thoroughness is how many paths you check. Excessiveness is how many observations you report. Maximize the first, heavily filter the second.

Severity must mean something. Blocker, High, Medium, Low, Warn, Nit. If everything lands at Medium, the scale is decorative. The validation pass explicitly downgrades: a race condition that only affects a debug-only code path is Low, not High.

Out-of-scope changes get flagged, not fixed. Unrelated changes get Warn severity—"you probably meant to do this, confirm"—not "change it."

Unrelated optimizations do not go in the review. The agent read three files and found an inefficient loop in one of them—untouched by this PR. It's a real inefficiency. It still doesn't belong, because a review comment is a request for the author to act. Asking them to fix code they didn't write in a PR about something else turns a two-day PR into a two-week PR. File it, mention it in chat, open a separate issue.

The nit question resolves the same way. A trailing-whitespace nit is not wrong—it's just not worth what it costs. Ten nits stacked above one Blocker is a formatting choice that hides a bug.

Thorough means the agent traced the deletion, read the callers, and checked the error path. Excessive means it told the author about formatting.

What to Do Now

Clone klaussy-agents, scaffold a skill for your repo, and set {{REPO_SPECIFIC_CHECKS}} by parsing your CLAUDE.md and .claude/rules/ files. Start with the five rules above. Expect your review agent to produce fewer comments that are more trusted. Measure precision, not recall.