<lp>

The Unsexy Problem of Code Review

Published:

I’ve had my head down for the last three months, building InsightAI at work, and I haven’t taken much time to come up for air and write about the heavier things I’m thinking about.

One of those things is code review in the age of AI.

AI has made us dramatically faster at producing code. It has not made us equally faster at understanding someone else’s code. For every author cranking out entire features with an agent, there is still a reviewer trying to understand what changed, why it changed, and whether it belongs in the codebase.

I experience both sides of this every day:

  1. The PR author, an AI-enabled developer completing larger changes faster than ever.
  2. The PR reviewer, the person standing between a feature branch and main, responsible for making sure the change leaves the codebase better than it found it.

The amount of code we can produce has changed. The responsibility attached to that code has not.

I don’t know that I have the answer to this problem. But I do think there are practical things we can do, especially inside mature enterprises that cannot rebuild their entire development process around the promise of a software factory.

Before getting into those practices, it seems worth returning to a more basic question: why do we review code in the first place?

Why do we do code reviews?

I almost forgot this section - and it may be the most important one.

In 1976, Michael Fagan published one of the earliest studies on formal code inspections at IBM. The process looked very different from the pull requests we use today, but the original idea is still relevant: find problems close to where they are introduced, before they become more expensive problems somewhere else.

That makes sense, but I don’t think finding bugs is the whole answer.

Google’s engineering practices put it this way - and it’s become a north star for me:

“The primary purpose of code review is to make sure that the overall code health of Google’s code base is improving over time.”

The part that stands out to me is over time.

The question isn’t only whether this change works. It’s whether the codebase is better after we merge it. Is it understandable? Is it maintainable? Did we make the next change easier or harder?

The research on modern code review points in the same direction. Bacchelli and Bird found that reviews produced knowledge transfer, team awareness, shared ownership, and alternative solutions. Rigby and Bird found that developers who participated in review became familiar with 66% to 150% more files, depending on the project.

That 66% to 150% number is the part I can’t shake. We talk about review like it is a gate, but some of its value comes from one more person learning how the system works.

Someone besides the author should understand the change. The team should learn something from it. And the codebase should be healthier after it is merged than it was before.

So what changed?

Code review has always required someone else’s time and attention. AI has made that imbalance harder to ignore.

As an author, building an entire feature with an agent can feel natural. I have the context. I watched the implementation take shape. I understand why one decision led to another.

Then I open the PR.

The reviewer doesn’t have any of that context. They have a diff, a (potentially AI-written) Jira ticket, a long AI-generated description that may or may not accurately explain the change, and a bunch of random markdown files that leaked through the iterative development process. They have to reconstruct what happened, determine what actually matters, determine what markdown is still accurate, and decide how much risk is hiding inside it.

This is where I think the conversation about AI-generated code can get too focused on whether the code itself is good or bad.

The reviewability problem often starts before the code. Was the original problem understood? Was it appropriately sized? Does the PR contain one concern or five? Are the objective checks automated? Does the description give the reviewer a useful map?

When those things aren’t true, reviewing becomes archaeology. The reviewer can skim it, block it, or spend hours rebuilding context that the author already had. None of those options scale particularly well.

I don’t think the answer is to slow developers back down. I think we have to get better at preparing, shaping, and routing the work that reaches another human.

Here are the things I’m actually doing

Before I go too far, I should acknowledge that some of my experience is probably confined to my daily driver: Bitbucket. Maybe other tools are handling this better. I have plenty of thoughts about Bitbucket, but I’ll save those for another day.

These are the things I can do without waiting for the industry, my company, or even my whole team to agree with me.

Start before the code

I went through the “five whys” exercise for several of the symptoms I was seeing in PRs, and I kept landing back at the same place: the problem and the Jira ticket.

If the problem isn’t understood or appropriately sized, the implementation probably won’t be either. AI can turn a vague ticket into a surprisingly complete feature, but “complete” doesn’t mean it solved the right problem.

I wrote in You Stopped Thinking that the bottleneck was never writing. It was thinking. Code review is where someone else inherits the cost when we skip that thinking.

So I am trying to slow down at the beginning. Get the problem right. Get it right-sized. Make sure the team is actually committed to solving it. If nobody else understands why the work matters, I probably shouldn’t be surprised when nobody feels responsible for helping it reach main.

Put the objective checks in the repository

A reviewer shouldn’t have to spend their attention finding something that a deterministic tool could have caught earlier.

That means putting the expectations as close to the code as possible: commit hooks, commit linting, static analysis, type checking, and tests. In Python, that might mean strict Ruff and mypy configurations. In TypeScript, it might include something like anti-slop.

These tools aren’t only for the developer anymore. They also guide the agent. If Ruff, mypy, or a pre-commit hook can complain about it, I don’t need a coworker spending their attention on it. And I definitely don’t need an agent generating another markdown file to explain it.

Put the expectation in the repository, make it deterministic, and let the human spend their time on the part that requires judgment.

Find the seams before opening the PR

I understand why someone working with Claude builds an entire feature end to end. I do it too. Sometimes that’s how I develop my own understanding of the problem.

But the way I explored the problem locally doesn’t have to determine how I present the change to a reviewer.

Google’s guidance on small changes defines a small change as “one self-contained change.” That is more useful to me than an arbitrary line count. The question is whether the work has seams: a refactor that can stand alone, a dependency change that doesn’t need to travel with the feature, or one part of the behavior that can be understood and merged independently.

I built a /pr skill to help me find those seams between local and remote Git. It also helps me write the description.

Google’s guidance on change descriptions asks for two basic things: what changed and why. That is usually what the reviewer needs. They don’t need an AI-generated essay that is longer than the code and only half as accurate.

The description should be a map, not a memoir.

Triage the PR before asking for human attention

I’ve also been experimenting with a triage agent named Bob.

Bob has the personality of a 20-year career veteran. He’s seen it all. His job is not to determine whether the code is correct, and he does not pretend to replace the reviewer.

He looks at the shape of the PR. How risky does it appear? Is it mixing unrelated concerns? Where are the seams? How much effort might it take to review? Then he offers the author a friendly tip or two before another person has to spend time on it.

The goal isn’t automated approval. It is to make the work arriving in the review queue easier for a human to understand.

Here are the things I want to explore

There are some things I can change on my own. Other things require the team to agree on how we want to work together.

I’m not going to pretend that we’re already doing these things for the sake of a blog post nobody will read. These are the ideas that stood out to me while researching the problem.

The shared agreement

When I read thoughtbot’s code-review guide, something clicked.

We have developers from different companies, teams, and stages of their careers. We may use the same pull request process without sharing the same understanding of what a review is supposed to accomplish.

What should block a PR? What is only a suggestion? How quickly should someone respond? Is the reviewer acting as a gatekeeper, a collaborator, or a teacher? What does the author owe the reviewer before requesting their time?

Even the language isn’t as obvious as I assumed. Thoughtbot recommends asking questions instead of making demands, explaining your reasoning, and being clear when you feel strongly about something. I’ve experienced varying versions of that throughout my career. I’ve even been told to “tell, don’t ask,” which I find increasingly appalling.

I know what a review feels like when every comment arrives as a command. The code becomes secondary, and the review turns into a weird little fight over who gets to be right. That’s not the kind of review I want to give or receive.

The guide also includes something I want to use more often in my own reviews. This simple phrase and the 30 seconds it takes to write it shows appreciation for the painstaking work the author did:

“I did not know about this. Thank you for sharing it.”

A team agreement doesn’t need to copy thoughtbot’s. But I think we need to agree on something. Otherwise, every PR becomes a negotiation over both the code and the review process itself.

Reviewer selection

My team has a default reviewer list with something like eight people on it. In practice, only a few of them regularly participate.

I wonder if assigning that many people creates the opposite of ownership. Everyone receives the notification, so everyone can assume someone else will handle it.

Shopify’s guidance asks more purposeful questions: Who has context? Who cares about the result? Who has the relevant expertise? And, perhaps most interestingly, who should learn this stuff?

That last question connects directly to the knowledge-sharing benefit of review. The best reviewer may not always be the person who already knows the most. Sometimes the review itself is how we spread that knowledge.

Meta’s reviewer recommendation system uses file ownership and reviewer availability to identify people who are both relevant and likely to respond. That’s interesting, but I probably don’t need machine learning to improve this. I could start by not assigning eight people and hoping one of them feels responsible.

Nudging and follow-up

Selecting the right reviewer doesn’t mean they will immediately have time to review the change.

Meta also built a Nudgebot for diffs that had been waiting too long. Instead of notifying everyone again, it identified the reviewers most likely to act and sent them the relevant context. Meta reported a 7% reduction in average review time and a 12% reduction in diffs waiting longer than three days.

The targeting is what interests me. A generic reminder is more noise. A reminder sent to the person who owns the next action, with enough context to act on it, might actually help.

I also wonder whether Bob could tell the reviewer what they are walking into. If the reviewer knew that a PR appeared low-risk and would likely take ten minutes, would it be easier to decide when to pick it up? And if the estimate said an hour, would that pressure the author to find another seam?

Risk-based merging

The most uncomfortable idea is that not every change may need a human reviewer.

Not every PR carries the same risk. A mechanical change with strong tests, deterministic checks, and an obvious rollback path is different from a change to authentication, access control, money movement, or a shared data model. Treating them identically adds work to the same queue without necessarily adding the same value.

A Dev Interrupted conversation about software factories described a company that allowed appropriately sized, well-tested changes to merge automatically. The interesting part wasn’t only the time saved. The policy gave developers a reason to keep changes small and make them easy to validate.

That makes me wonder whether auto-merge could be an incentive instead of merely an escape hatch.

If a change is focused, covered by meaningful tests, accepted by deterministic tooling, and classified as low-risk, should it have to wait for a human approval? Where should that line be drawn? And what evidence would we need before trusting it?

That question leads directly to the much bigger claim floating around the industry: maybe the pull request itself is going away.

The elephant in the room

I couldn’t write this without acknowledging what I see happening across the industry.

There are teams building software factories that take work from an issue, through planning and implementation, into testing, review, and production. This isn’t entirely hypothetical. Factory describes a system where work flows from a signal to production autonomously, with humans defining rules, risk tiers, and validation gates.

I believe this is real in some places. I also know how much has to be true underneath it: clear ownership, reliable tests, trustworthy deployments, and a team willing to let the system act. “We added coding agents” is not the same thing as building a software factory.

And people are starting to say the quiet part out loud: maybe pull requests are dead.

An upcoming USENIX talk is literally titled “Pull Requests Are Dead, Long Live Peer Review.” The argument is interesting. When an agent writes code that the author hasn’t really read, asking another human to review it starts to feel less like peer review and more like asking them to audit a machine. Their team moved human review earlier, to the implementation plan, and left the PR review to agents.

I think there is something important in that.

The pull request isn’t the point. Human judgment applied at the right place is the point. If the important decisions happen in the problem definition, constraints, architecture, and implementation plan, maybe that is where our attention belongs.

But “software factory” doesn’t describe one agreed-upon workflow. Some aim for autonomous delivery. Others still produce a pull request for a human to approve. Guild’s software factory, for example, uses separate agents to plan, implement, and review a change, but still hands the resulting PR to a person for the final decision.

So I don’t think the interesting question is whether we preserve the pull request forever.

The question is what replaces the things we were using it for.

How does the team protect code health over time? How does knowledge spread beyond the person who requested the change? Who understands what entered the system? Who owns it when it breaks? How do we know the implementation matches the original intent? What evidence tells us that an agent reviewing another agent is enough?

Maybe a mature software factory has good answers to all of those questions. If it does, I don’t particularly care whether it uses a pull request.

But today, in the office of a mature enterprise, I’m not sure we’re there yet.

I see legacy systems, incomplete test coverage, unclear ownership, and workflows that were built for a different pace of development. Adding more agents can increase throughput, but it can also increase the amount of code moving through a system that nobody fully understands.

I’m not interested in defending pull requests because they are familiar. I’m also not ready to declare them dead because an agent can write the code and another agent can approve it.

Maybe the pull request goes away. The responsibility doesn’t.

Resources