Skip to main content
Tooling & Automation

Why Your Linter Isn't a Code Reviewer: What Automation Can and Can't Fix

We rely on linters and static analyzers to gate merges, but they can't judge design. Here's where automation should stop and human review should take over.

Should I let my linter block the merge?

You've probably typed that into a search bar after a CI failure. Or asked it in a team chat when the linter flagged something you thought was fine. The short answer is: yes, but only for the things that are actually mechanical. The longer answer is that if you're using a linter or static analyzer to gate your merges, you're likely doing both the machine and your human reviewers a disservice. We need to draw a line between what the tool should catch and what a human must judge, and that line is not where most teams put it.

In practice, we've seen teams treat their linter like a bouncer: if it passes, the PR is good. That's a mistake. The machine is not a code reviewer, and it never will be. But it can be the best assistant your reviewer ever had, if you set it up right. Let's walk through what automation can actually do, where it falls short, and how we can build a review process that respects both.

The machine's domain: style, syntax, and the boring stuff

There's a whole class of feedback that a human should never have to give. Formatting, unused variables, missing semicolons, a function that's too complex by a strict formula—these are things a tool can check with perfect consistency, and they're things that, when left to humans, cause the most annoying review comments. Google's engineering practices are blunt about this: linters, formatters, static analysis, secret scanners, and dependency scanners should handle style and mechanical checks so that human review can focus on logic and architecture (Google Engineering Practices).

So what does that actually look like? For Python, Bandit will parse your code into an AST and run plugins against it to flag common security issues (Bandit). For Go, gosec will scan the AST and SSA representation, even doing taint analysis for SQL injection and path traversal (gosec). For JavaScript, ESLint is pluggable to the point that every rule is a plugin (ESLint). And then there's SonarQube, the platform that bundles reliability, security, maintainability, coverage, and duplication into a single dashboard (SonarQube). The point is, we have an arsenal.

But here's the thing: a linter can tell you that a function has a cyclomatic complexity of 15, because that's a count of paths through the code (SonarQube). It can't tell you that the function's design is wrong. It can't tell you that the author took a shortcut that will bite the next person who touches this code. That's a human judgment call. And it's not just about complexity—it's about whether the change fits the overall architecture, whether it's testable, whether it's the right approach at all. Those are things that no tool, no matter how sophisticated, can assess.

So let's set a rule: the machine owns the mechanical, the human owns the judgment. And the way to enforce that is with a quality gate that blocks merges on the mechanical stuff, but never on judgment calls. SonarQube's built-in quality gate is a good model: no new issues, all security hotspots reviewed, new code coverage at least 80%, and duplication in new code under 3% (SonarQube). That's a gate that a machine can fairly apply. It's not perfect, but it's a baseline.

When automation gets it wrong: the design gap

Here's where the trouble starts. We've seen teams set up a pipeline where the static analysis has to pass or the PR is blocked. That's fine for the mechanical stuff, but what about a change that intentionally adds a bit of complexity for a good reason? Or a change that adds a new dependency because it's the right trade-off? A tool can't weigh that. It can only say "this violates a rule." And if you've made that rule unbreakable, you've taken the judgment out of the hands of the humans who are supposed to be doing it.

Google's research shows that at scale, the median change is about 24 lines, and 90% touch fewer than 10 files (Google case study). That's a world where the mechanical checks are fast and the human review is focused. But if your review process is spending time on style, you're wasting the one resource that can't be automated: the reviewer's attention. We have to protect that.

There's also the issue of false confidence. If your linter passes, you might assume the code is safe. But OWASP's current top risk is broken access control, which is a design flaw, not a pattern that a linter can spot (OWASP). A tool can flag a SQL injection if it sees string concatenation, but it can't tell you that your authorization checks are missing. That's a human review. And that's why we need to be careful about what we automate.

The hard truth is that automation can catch the known, but it can't anticipate the unknown. The OWASP Top 10 is a list of patterns, but the real vulnerabilities often come from how those patterns are combined in a specific codebase. A human reviewer with context can see that this particular endpoint is missing an access control check because they know the business logic. A tool can't.

Building the right pipeline: a comparison of approaches

So how do we actually build a review process that uses automation without letting it take over? Let's compare three common setups:

ApproachWhat it gatesWhat it misses
Linter only, no gateNothing—just flags styleEverything. No enforcement, no quality bar.
Static analysis gate (e.g., SonarQube quality gate)Mechanical issues, coverage, duplicationDesign flaws, architectural issues, business logic errors
Full pipeline: static analysis + required human approvalMechanical issues + a human sign-offVery little, if the human is actually paying attention

The third option is what we should aim for. But it's not enough to just require an approval—you need to make sure the human approval is meaningful. That means branch protection rules that require a specific number of approving reviews and can dismiss stale approvals when new commits change the diff (GitHub). It means using code owners to route the review to the right people automatically (GitHub). And it means giving reviewers the tools to do their job quickly.

Google's data shows that a median review gets a first response in under an hour for small changes, and that the overall median latency is under four hours (Google case study). That's the speed we should be targeting. But you can't get there if reviewers are spending time on things a machine should have caught.

So here's a concrete example. You're reviewing a Python PR that adds a new API endpoint. The linter passes, SonarQube says coverage is 85%, and there are no new issues. But the code has a SQL query built with f-strings. Bandit should catch that, but maybe it didn't because the pattern is subtle. That's where the human reviewer comes in. They see the f-string and flag it. The machine didn't catch it because it's a variation on a known pattern. That's the kind of thing a human is essential for.

But if the reviewer is also checking whether the code follows PEP 8, they're wasting their time. The linter already did that.

Quick tip: separate approval from comment

Here's a small practice that makes a huge difference: use Google's "LGTM with comments" approach. If your review is done and the remaining comments are minor—a typo, a suggestion, a refactor you'd like but don't require—approve the change and leave the comments. Don't block the merge on nits. This is a documented Google practice that speeds up the whole process without sacrificing quality (Google Engineering Practices - Speed).

The bottom line

The single most important thing to remember is this: automation should handle the mechanical, and humans should handle the judgment. If you're letting a tool block a merge on a style issue, you're doing it wrong. If you're letting a human review a change without a tool having already checked the mechanical stuff, you're also doing it wrong. The best review process is one where the machine is the first line of defense, and the human is the final judge. That's the only way to keep code health improving over time, which is, after all, the primary purpose of review (Google Engineering Practices).

Sources

  • Google Engineering Practices (code review) - https://google.github.io/eng-practices/review/
  • Google Engineering Practices - Speed of Code Reviews - https://google.github.io/eng-practices/review/reviewer/speed.html
  • Modern Code Review: A Case Study at Google (ICSE-SEIP '18) - https://www.papercache.org/papers/mlsys/system/2026/03/25/modern-code-review-a-case-study-at-google
  • SonarQube Server Docs - Understanding quality gates - https://docs.sonarsource.com/sonarqube-server/2026.1/quality-standards-administration/managing-quality-gates/introduction-to-quality-gates
  • Bandit (PyCQA) Docs - https://bandit.readthedocs.io/en/latest/
  • gosec - Go Security Checker - https://raw.githubusercontent.com/securego/gosec/master/README.md

Share this article:

Comments (0)

No comments yet. Be the first to comment!