# Code Review Practices

> The single highest-leverage convention in code review is prefixing every comment

- Skill: `snoodleboot-io/code-review-practices` (Agent Skill)
- Install (CLI): `npx skillmds@latest add snoodleboot-io/code-review-practices`
- Raw SKILL.md: https://api.skillmd.com/api/skills/snoodleboot-io/code-review-practices/raw
- Safety review: PASS (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: snoodleboot-io (https://skillmd.com/u/snoodleboot-io)
- Updated: 2026-08-19
- Page: https://skillmd.com/skills/snoodleboot-io/code-review-practices

---


# Code Review Practices (Verbose)

## Core Patterns

### Severity Labels Make Reviews Unblockable

The single highest-leverage convention in code review is prefixing every comment
with its severity. Without it, the author must reverse-engineer your intent, and
the safe default is to treat everything as blocking — which is how a two-nit
review turns into another round trip.

| Prefix | Meaning | Author may merge? |
|---|---|---|
| `blocking:` | Correctness, security, data loss, or a contract break | No |
| `question:` | I don't understand; the answer may become blocking | Answer first |
| `suggestion:` | A better approach; author's call | Yes |
| `nit:` | Style, naming, phrasing | Yes, ignore freely |
| `praise:` | Worth keeping and imitating | Yes |

Discipline for reviewers: you may only write `blocking:` if you can name what
breaks. "I'd have done it differently" is `suggestion:`. If your blocking rate
exceeds roughly one in five comments, you are inflating severity and the labels
stop carrying information.

### Rewriting Real Comments

Most bad review comments share one flaw: they describe the reviewer's reaction
rather than the change required.

```
❌ "This is hard to follow."
✅ "suggestion: `process()` on 40-95 does parse, validate, and persist.
    Splitting out `_parse_payload` would let the validation tests drop the
    DB fixture. Not blocking — fine as a follow-up."

❌ "Why did you do it this way?"
✅ "question: Curious why polling here rather than the existing webhook
    on `OrderUpdated`. If it's because the webhook is unordered, worth a
    comment in the code — the next person will ask the same thing."

❌ "Bug."
✅ "blocking: If `items` is empty, line 63 divides by `len(items)` and
    raises ZeroDivisionError. Empty carts reach this path from the
    abandoned-cart job."

❌ "We don't do it that way here."
✅ "suggestion: The other three handlers use the `@retryable` decorator
    (see `payments/handlers.py:22`) rather than a manual loop. Matching
    that gets you the metrics wiring for free."
```

Note what the good versions share: a line number, a concrete failure or concrete
alternative, and an explicit stance on whether it blocks.

### Size Drives Everything

Review effectiveness is a function of diff size, and the falloff is steep:

| PR size (changed lines) | Typical outcome |
|---|---|
| < 100 | Careful line-by-line reading; highest defect yield |
| 100-400 | Still effective; the practical target band |
| 400-800 | Reviewer skims; structural feedback only |
| > 800 | Approval rate goes up, comment density goes down |

The counterintuitive part: large PRs receive *fewer* comments per line, not
more. A reviewer's attention budget in one sitting is roughly 200-400 lines and
about 60 minutes; past that, defect detection falls off sharply. Treat these as
norms to calibrate against rather than hard thresholds — the shape of the curve
matters more than the exact number. So "LGTM" on a 1,200-line PR is not a signal
of quality, it is a signal of exhaustion.

Splitting is a design activity, not a formatting one. Cut along seams that each
leave the system working:

```
❌ One PR: "Add team billing"  (1,400 lines)

✅ 1. Migration: add `billing_accounts` table, no readers yet    (~90)
   2. Repository + unit tests, unreferenced by any endpoint      (~180)
   3. Service layer wiring behind a disabled feature flag        (~250)
   4. Endpoint + flag enable                                     (~200)
```

Each lands independently, each is reviewable in a sitting, and a rollback of
step 4 does not strand the schema.

### Latency Dominates Cycle Time

Measure the wall-clock life of a PR and the review *waiting* time usually
dwarfs both authoring and reviewing. A change that takes 3 hours to write and 25
minutes to review can easily take 3 days to merge. Every day it waits, the
branch drifts, the author context-switches, and the eventual re-read costs more.

Practical norms that move the number:

- First response within 4 business hours. "Looking this afternoon" counts.
- Review incoming PRs before starting new work of your own.
- Approve-with-comments for anything that is only `nit:` and `suggestion:`.
  Trust the author to apply them; do not require another round.
- Escalate to a synchronous 10-minute call after the second round trip on the
  same disagreement. Text is a bad medium for design arguments that have already
  failed once.

### What Humans Should Review

Anything a machine can check should already be checked before a human looks.
Formatting, import ordering, lint rules, type errors, and test failures belong
in CI. Human attention is the scarce resource; spend it on judgment:

- **Problem fit** — is this solving the right problem, at the right layer?
- **Failure paths** — timeouts, retries, partial writes, concurrent callers.
  What is the state of the world if this process dies at line 40?
- **Data lifecycle** — is the migration reversible? Is there a backfill? What
  reads the old column?
- **Future cost** — will this make the next change harder? Review for the change
  you would have to make later, not for the change you would have written.
- **Test meaning** — do the tests fail if the behavior regresses, or do they
  merely execute the code? (See `test-coverage-categories`.)

Security-sensitive diffs get their own dedicated pass; see `secure-code-review`.

### Author-Side Practice

Reviews are largely won before anyone else reads the code.

Write the description for a reader who lacks your context:

```
❌ "Fixes the checkout bug."

✅ Why: Checkout 500s when a promo expires between cart load and submit.
       ~40/day, PRO-412.
   What: Re-validate the promo at submit time rather than trusting the
       cart snapshot.
   Alternative rejected: locking the promo at cart creation — would hold
       inventory for abandoned carts.
   Risk: adds one read to the submit path (~3ms, measured).
   Testing: new test for the expiry race; verified manually against staging.
```

Then read your own diff on the PR page and annotate it. Flag the file that is a
pure mechanical rename so the reviewer skips it. Explain the odd-looking early
return. Every self-comment you leave is a round trip you did not spend.

## Common Anti-Patterns

❌ **Unlabeled comments** — the author cannot tell a nit from a defect and
treats all of them as gates.
✅ Prefix every comment: `blocking:`, `question:`, `suggestion:`, `nit:`.

❌ **The rewrite review** — reviewer reconstructs the change as they would have
written it, and the author complies to get unblocked.
✅ Accept any design that is correct and maintainable. Reserve objections for
things that are wrong or that make future work harder.

❌ **Reviewing formatting by hand** — comments about spacing and naming style
that CI could enforce, crowding out substantive feedback.
✅ Move it into the linter. If the team argues about it twice, automate it.

❌ **The batched mega-review** — a week of work lands as one 1,500-line PR.
✅ Stack it. Four sequential PRs of 200-400 lines each, merged as they pass.

❌ **Silent approval on a big diff** — LGTM in 90 seconds on 900 lines.
✅ Say what you actually reviewed: "Read the service layer and migration
closely; skimmed the generated client. Someone should check the retry logic."

❌ **Resolving disagreements in DMs** — the decision exists only in two people's
memory, and a third engineer relitigates it next quarter.
✅ Have the conversation wherever it happens, then post the conclusion and its
reasoning back on the PR.

❌ **The drive-by blocker** — a reviewer with no context blocks on an
architectural objection to a design already agreed in review.
✅ Raise architecture at design time. If it genuinely must be reopened, say so
explicitly and take it off the PR thread.

❌ **Nitpicking a hotfix during an incident.**
✅ Review incident fixes for correctness and blast radius only; file the cleanup
as a follow-up ticket.

## Code Review Checklist

**As reviewer**
- [ ] Responded within 4 business hours of the request
- [ ] Every comment prefixed with a severity label
- [ ] Each `blocking:` names a concrete failure, not a preference
- [ ] Checked failure paths: timeout, retry, partial write, concurrency
- [ ] Checked the migration is reversible and the backfill is bounded
- [ ] Verified tests would actually fail on regression
- [ ] Left no comment a linter could have made
- [ ] Stated what you did and did not review if the diff is large
- [ ] Approved with comments when nothing is blocking

**As author**
- [ ] Diff under ~400 changed lines, or split with the stack explained
- [ ] Description covers why, what, alternatives rejected, risk, testing
- [ ] Self-reviewed the diff and annotated the non-obvious parts
- [ ] Mechanical changes (renames, generated files) separated or flagged
- [ ] Responded to every comment, including "won't fix, because…"
- [ ] Off-thread decisions written back onto the PR before merge

