Engineering notes · Hendrik Krack

What AI code reviewers catch — and what they still miss

A frank account of where AI code review earns its keep and where it quietly fails — and how to close the gap.

Published June 2026 · 8 min read

The honest case for AI review

AI code review tools are genuinely useful. I use one at work every day — I help build it, which means I'm also the person most qualified to tell you where it falls down. The marketing says these tools will catch everything a senior engineer would catch and also never sleep and also never leave passive-aggressive comments in your PR. Two of those three claims are roughly true.

Here is what I actually believe: AI reviewers are net positive on almost any team that tries them seriously. They close a real gap — the stuff that humans consistently miss because they are tired, or rushing, or have been staring at this codebase for so long that the obvious bug has become invisible. They make review coverage consistent. They flag things at 11 PM on a Friday with the same attention they gave the Monday morning PR. That matters.

What they are not is a drop-in replacement for a thoughtful senior engineer. They are more like a very fast, very well-read junior who has read every CVE, every OWASP article, and every style guide ever written, but has never shipped anything and has no idea what your product is supposed to do. That combination is useful. It is also a specific combination with specific failure modes — and knowing those failure modes is the only way to get full value out of the tool.

"An AI reviewer is a fast, well-read junior who has never shipped anything and has no idea what your product is supposed to do. That's still useful. It's just not the same as a senior." — Hendrik Krack

What they're genuinely good at

The wins are real and they are consistent. Here's the category breakdown from my experience, roughly in order of reliability.

Pattern-matched bugs. Off-by-one errors in loop bounds. Null dereferences where a value could be absent but the code assumes it isn't. Incorrect operator precedence that's obvious once flagged but invisible when you're writing. Returning the wrong variable at the end of a function after a refactor. These are the kinds of bugs that training data is full of, and AI reviewers catch them at a rate that would embarrass a tired human reviewer.

Common security mistakes. SQL injection from string-concatenated queries. XSS from unescaped user input rendered as HTML. Hardcoded credentials in source. Overly permissive CORS configs. Insecure deserialization. JWT verification being skipped or done wrong. These are well-documented, well-catalogued, and AI reviewers have effectively memorized the catalog. If your diff touches a database query or an auth flow, there is a good chance the reviewer will check the obvious things — and that alone is worth the subscription.

Style drift and consistency. When the codebase uses one naming convention and a PR introduces another. When a new enum case is handled in three of the four switch statements that need it. When an error message is in a different format from every other error message. Humans notice this inconsistently because we unconsciously adapt to local context. AI reviewers notice it reliably because they hold the whole diff in view at once.

Missing null checks and incomplete input validation. The function that handles a webhook payload and accesses payload.user.id without checking that payload.user exists. The CLI flag that gets passed directly to a shell command without sanitization. These are simple pattern matches, and simple pattern matches are exactly what AI reviewers are built for.

Here's a concrete example of a bug an AI reviewer will catch reliably:

// BUG: AI reviewer WILL catch this
// Missing null check before accessing nested property
function getDisplayName(user) {
  // If user.profile is null (e.g. OAuth user with incomplete signup),
  // this throws "Cannot read properties of null (reading 'displayName')"
  return user.profile.displayName || user.email;
}

// ALSO a bug: AI reviewer WILL catch this
// SQL injection via string concatenation
function getUserByEmail(email) {
  return db.query("SELECT * FROM users WHERE email = '" + email + "'");
}

// BUG: AI reviewer will likely MISS this
// Semantically wrong: this returns the *last* matching config,
// not the most *specific* one. In a config hierarchy where more
// specific entries appear earlier, this silently returns the wrong value.
function resolveConfig(keys, configs) {
  let result = null;
  for (const cfg of configs) {
    if (keys.every(k => cfg.scope.includes(k))) {
      result = cfg; // should be: return cfg (first match wins)
    }
  }
  return result;
}

The first two examples are structural — they have a shape that maps to known bug classes. The third is semantically wrong: it produces a result, it produces a result every time, and it will pass every basic test you write unless you specifically test a case where scope ordering matters. An AI reviewer looking at the function in isolation sees a loop that accumulates a value. It does not know that your config resolution is supposed to prefer more specific entries.

The structural blind spots

This is where it gets more interesting, and where the overselling does the most damage. AI reviewers operate on the diff in front of them. They don't see what changed in the last three PRs that set up this one. They don't have a model of your domain. They can't distinguish "this is a risky change" from "this is a boring change" based on what the system actually does — only on the syntactic shape of the code.

No visibility into intent. The reviewer doesn't know whether this PR is supposed to be a refactor or a feature change. If the diff happens to touch both, it has no way to flag that the refactor quietly altered behavior, because it has no pre-diff model of what behavior was expected.

No cross-PR context. A PR that looks fine in isolation can be the third step in a sequence where the first two steps introduced a subtle invariant. The reviewer sees this PR. It doesn't know that two weeks ago you changed how user sessions are initialized, and that the code it's reviewing now quietly relies on the old behavior.

No domain model. This one stings in practice. If you're building a financial system, the reviewer does not know that an amount field can be negative (representing a credit) and that treating it as unsigned somewhere in the code is a serious business logic bug. It might flag it if the field is named something obviously financial, but if it's called delta, you're on your own. The domain lives in your head and your team's heads. It is not in the diff.

Emergent complexity across files. The reviewer looks at the changed files. Architectural debt that lives in the interaction between ten files — each fine in isolation, collectively a mess — is invisible to a tool that only sees the files you changed. It won't tell you that you're adding a fourth way to handle the same thing because the first three are in files you didn't touch.

The latent bug problem

There's a specific class of bug I think about a lot because it's the one that makes teams falsely confident about their AI review coverage. I call these latent bugs — code that is syntactically fine, passes all the tests, and gets flagged by neither the human reviewer nor the AI reviewer. It's wrong, but it's wrong in a way that only manifests under a specific combination of conditions that nobody thought to test.

The example above — returning the last matching config instead of the first — is a mild version. In production you find these in concurrency code where two goroutines write to shared state under conditions that the test suite never exercises. You find them in numeric code where rounding behavior changes at a specific magnitude and all your test fixtures happen to use small numbers. You find them in state machines where a particular transition sequence, perfectly valid according to the spec, was never tested because it requires four specific things to happen in order.

AI reviewers miss these bugs for different reasons than humans do. A human misses them because they're tired, or they've been staring at the codebase too long, or the code looks similar to correct code they've seen before. An AI reviewer misses them because they can't evaluate the semantic contract — the gap between what the code does and what the system requires it to do. That gap lives outside the diff, and right now outside the reach of any automated reviewer.

This is not an argument against AI review. It's an argument for not treating it as a substitute for the kind of thinking that asks: "What is this code supposed to do, and are there conditions under which it does something else?" That question requires knowing what the code is supposed to do. No automated tool is in the room when you make those decisions.

How to configure your reviewer to cover more ground

Most teams using AI code review are leaving coverage on the table because they're running the tool with default settings against a codebase the tool knows nothing about. Here's what actually moves the needle.

Custom rules for your domain. If your system has invariants — "payment amounts are always in cents, never in decimal dollars" or "this field is nullable in the database but should never be null by the time it reaches this layer" — write them down as rules. AI reviewers that support custom rules (CodeRabbit included) can enforce these consistently in a way that a general model trained on generic code can't. The domain knowledge you encode is coverage you would otherwise only get from someone who has been on the team for two years.

Focus areas by path. Not all code deserves the same review intensity. Your auth module and your billing logic deserve hard scrutiny on every change. Your data migration scripts for a deprecated feature probably don't. Routing the reviewer to apply different rigor by file path is a straightforward configuration that most teams skip.

Severity tuning. If your reviewer is flagging every inconsistent variable name at the same severity as a potential SQL injection, you'll train your team to ignore the noise — and then they'll ignore the real things too. Turn the signal-to-noise ratio up. Opinions about code style should be suggestions; security findings should be blockers. The default severity settings are calibrated for a generic codebase. Tune them for yours.

Give it context about what changed recently. Some tools let you attach context to a PR — a description of what this change is supposed to do, what invariants it should maintain, what you deliberately chose not to do and why. This is the closest thing to closing the intent gap. It doesn't fully solve the problem, but a reviewer that knows "this PR replaces the legacy session store; the new one behaves identically except that session expiry is now in UTC" will do a materially better job than one reviewing a raw diff.

What will always need a human

I'll be direct about this because I think the honest version of it is more useful than a diplomatic hedge: the things that require a human are the things that require judgment about purpose. Not pattern matching, not consistency checking, not "have I seen this bug class before" — those are all learnable from data. The thing that isn't learnable from data is understanding what this specific system, built by this specific team, for this specific set of users, is supposed to do and why.

Intent. A reviewer who wasn't in the design meeting cannot know that a particular simplification, however elegant, breaks an implicit guarantee the system makes to its callers. The AI reviewer sees code that looks fine. A senior engineer who was in the room knows it's wrong.

Taste. Whether an abstraction is the right one. Whether this is the moment to introduce a new concept or the moment to stay boring. Whether the implementation, while correct, is going to be a maintenance nightmare in six months. These are aesthetic judgments that require a model of the future the tool doesn't have.

Institutional knowledge. The comment that says "this looks redundant but don't remove it — it papers over a bug in the third-party library, see ticket #4821." The reason the code does it the hard way. The context that makes "why is this here" answerable. None of this is in the diff. It lives in your team's memory, your Slack history, your incident post-mortems. Until we solve knowledge retrieval for codebases in a way that actually works, this stays with humans.

The right mental model for AI code review is not "this replaces human review." It's "this does the exhausting, consistent, pattern-matching work so your human reviewers can spend their time on the things that actually require human judgment." Used that way, it makes your review process both faster and better. Mistaken for a replacement, it introduces a false sense of coverage that's more dangerous than no coverage at all.

The AI–human review spectrum
Fig. 1 — What AI review handles well on the left, where human judgment is irreplaceable on the right. The middle band is where configuration and context narrow the gap.

— Hendrik