AI vs static analysis — when to use which (and why I use both)

A comment under my last post made me rethink where AI ends and static analysis begins. Here is how I draw the line — based on a system I run in production.

Apr 21, 2026~7 min read
AI vs static analysis — when to use which (and why I use both)

A colleague left a comment under my last post that I haven't stopped thinking about:

"Some of this can be fully automated without AI. Static code analysis goes a long way — from simple pattern matching to architecture tests."

He's right. And that's exactly the point.


The wrong question

Most discussions about AI in engineering workflows frame it as a replacement decision: do I use AI or static analysis? That's the wrong frame.

The right question is the same one I ask when tiering models in my agent system:

What kind of thinking does this task actually need?

Static analysis and AI agents are not competitors. They operate on different problems. Conflating them is how you end up either dismissing AI entirely or — worse — using it for tasks where a linter would do the job in milliseconds for free.


What static analysis does better than AI

Pattern matching at scale, deterministically, with zero hallucinations.

TaskTool
Import convention enforcement (<Link> not <a>)ESLint rule
Naming convention violationsESLint / custom rule
Unused variables, dead codeStatic analysis
Dependency boundary violationsArchitecture tests (e.g. ArchUnit, Dependency Cruiser)
Type coverage gapsTypeScript compiler
Known anti-patterns with binary answersLinter rules

These tasks share one property: the answer doesn't depend on context. Either the import is wrong or it isn't. Either the variable is unused or it isn't. No reasoning needed.

For these, static analysis is faster, cheaper, more reliable, and completely deterministic. There's no reason to involve AI.

I'll be direct: in my own system, three of the nine agents — TypeScript Standards, Styles & CSS, and the Storybook Components Enforcer — run on Haiku specifically because their job is pattern matching. They don't reason. They match. And if I could replace parts of their work with a well-written ESLint rule, I would. Some of that work probably should be a linter.


Where static analysis hits a wall

The wall appears the moment the answer depends on context.

Here's a real example from my Security agent:

A function passed every ESLint rule. No any. No missing null checks. Clean types throughout. Static analysis had nothing to flag.

But the data from an unvalidated form was flowing — through two layers of abstraction — directly into a raw query.

A linter sees the node. An AI agent sees the path.

"Is this an XSS vector?" is not a binary question. The answer depends on how that value was produced, where it came from, and what happens to it downstream. That's reasoning — and it's exactly what Sonnet does in my Security agent.

The same logic applies to:

  • Algorithm & Performance — "Does this re-render unnecessarily?" requires understanding component lifecycle, not just syntax
  • Code Reuse — "Is this logic already implemented in shared utilities?" requires knowing the codebase
  • React architecture — "Is this hook introducing a hidden dependency cycle?" requires contextual judgment
  • Storybook Components Enforcer — "Did the developer use the right design system component?" requires comparing the diff against what's defined in Storybook

None of these questions have a binary answer. All of them require context. Static analysis can't help here.


Three findings from the last weeks

To make this concrete — here are three things my agents caught recently that no linter would:

An algorithm that worked fine today but would choke at scale. A search loop that handled current data without issues, but would collapse the moment the dataset doubled. The Algorithm & Performance agent flagged the complexity. ESLint had nothing to say — the code was syntactically clean.

A new hook written from scratch — when an almost identical one already existed in the codebase. We have shared hooks in the project that handle things like filters. A developer wrote a brand-new hook right next to them, doing essentially the same job. The Code Reuse agent recognized the duplication across files. A linter sees individual files in isolation. An agent sees the codebase.

A nested ternary chain that was technically valid but painful to maintain. Three levels deep, perfectly typed, ESLint-clean. The React agent flagged it as a readability problem that would slow down every future reviewer. Static analysis can have nested-ternary rules, but tuning them not to flag every short ternary is hard. An agent can use judgment.


The actual decision matrix

I now think about tooling this way:

Question typeToolWhy
Binary — right or wrong, no contextStatic analysis / linterDeterministic, fast, free
Pattern matching with known shapesHaiku-tier agent or linterCheap, accurate enough
Context-dependent reasoningSonnet-tier agentRequires understanding relationships
Cross-cutting synthesisOrchestrator (Opus)Requires large context and judgment

The moment I catch myself writing an AI prompt for something that could be an ESLint rule, that's a signal. Not that AI is bad — that I'm using the wrong tool.


Why I use both

My system has nine agents and a codebase with ESLint, Prettier, TypeScript strict mode, Stylelint, and Dependency Cruiser running on every commit. They don't conflict — they cover different layers.

Static analysis runs first. It's fast, it's in the CI pipeline, it catches the obvious things before they reach code review at all.

AI agents run on the MR diff. By the time an agent sees the code, the mechanical errors are already gone. The agents can focus entirely on reasoning tasks — the things that can't be expressed as a rule.

This is the same logic as model tiering. Don't use a senior architect to check variable names. Don't use an AI agent to enforce import conventions.

Match the cost of thinking to the complexity of the task.

Addy Osmani argues in Code Review in the Age of AI that properly configured AI reviewers catch 70-80% of low-hanging fruit, freeing humans to focus on architecture and intent. Martin Fowler calls the wider concept harness engineering — combining deterministic tools with AI agents around the model itself.

Both make the same point: layers, not replacements.


What this means if you're building something similar

If you're building an AI code review system — or any agent-based tooling — I'd suggest auditing your agents the same way:

  1. Could this finding be expressed as a linter rule? If yes, write the rule instead.
  2. Does the answer depend on context beyond the current file? If yes, you probably need an agent.
  3. Is your agent flagging things a grep would catch? That's a signal your prompt is too broad.

The agents that generate the most value in my system are the ones that catch things no other tool can catch. That's the standard I hold them to.

Static analysis isn't the competition. It's the foundation.


This post is part of a series on building a multi-agent AI code review system with Claude Code:

Was this helpful?

AI vs static analysis — when to use which (and why I use both) | Code Nomad