Static Analysis

What Static Analysis Really Costs, Once You Count the Triage

Static analysis costs a team three separate bills, and the one with a price tag is usually the smallest. There is the tooling — often zero, because the strongest linters and type checkers in every major ecosystem are open source, and paid scanners are typically priced per developer or per contributing committer rather than per scan. There is CI compute, which is scan duration multiplied by how often you scan. And there is engineer attention: the hours spent reading findings, deciding which are real, and fixing or suppressing them. That third bill dominates, especially in the first month, and it is the one nobody budgets for.

Which means the useful question before you adopt anything is not "what does the tool cost." It is how many findings will this produce, against how much code, and who reads them. Get that answer right and a paid scanner is cheap. Get it wrong and a free tool becomes the most expensive thing in the pipeline, because the team quietly stops reading its output.

The three bills, in the order they surprise people

Tooling. A one-off decision with a predictable number attached, or no number at all. Teams research this bill hardest and it matters least.

CI compute. Scan time × pull-request volume × jobs per pull request. Small per run, relentless in aggregate, and it grows with the team rather than with the codebase. It also has a second, larger cost hiding inside it: every minute added to the pull-request wait is a minute of somebody's flow, multiplied by everyone.

Triage. Wildly variable, front-loaded, and driven almost entirely by how much unreviewed history the first scan meets. A green-field repo produces a handful of findings; a decade-old service scanned at full strictness produces a wall of them, and the wall is what kills adoptions.

Those ratios shift as a rollout matures. Early on, triage is nearly the whole cost. A year later, if you set it up well, all three are small — and if you didn't, the tool is still running in CI and nobody has read a report since the second week.

How static-analysis tools are priced

Four pricing shapes cover most of the market, and each one tells you what will make your invoice grow.

Free and open source. Most linters, formatters and type checkers, and a fair number of security scanners. There is no licence, which is not the same as no cost — see the next section.

Per developer, or per contributing committer. The most common commercial model for hosted scanners. Note the difference: "developer" usually means seats you provision, while "contributor" is often measured from your git history over a rolling window, which quietly includes contractors, bots that commit, and people who left. If a vendor prices by contributor, look at what your repository history actually reports before you estimate anything.

By lines of code, or by repository. Less common, and it prices the artefact rather than the team. It punishes monorepos and vendored directories, and rewards excluding generated code — which you should do anyway, because scanning generated files produces findings nobody can act on.

Open core. The engine is free; the parts that make it usable at team scale — dashboards, historical trends, policy management, single sign-on — are paid. This is the model most likely to surprise you six months in, when the free engine is embedded in five pipelines and the reporting you need sits behind the upgrade.

For picking between tool categories on capability rather than price, what static analysis covers sets out where linters, type checkers and security scanners stop overlapping.

Does open source make it free?

No, and the gap is worth stating precisely, because "it's free" is how most over-budget rollouts start.

An open-source scanner removes the licence and leaves everything else: the compute it uses, the configuration nobody owns, the upgrade that changes rule defaults and reopens findings you had closed. Self-hosting a server-based analyser adds infrastructure and the operational duty with it — someone patches it, someone restores it, and that someone is on your team.

The honest framing is that paid tools convert an unpredictable time cost into a predictable money cost. For a small team with no capacity to maintain a scanning platform that is usually an excellent trade; it stops being one when the thing you bought was reporting nobody opens.

What drives the CI bill up

Compute cost is the most controllable of the three, and the levers are boring and effective:

  • Scan scope. Whole repository on every push is the default that costs the most. Scanning only what changed is far cheaper and far less informative about the rest of the repo — a real trade-off, not a free win.
  • Analysis depth. Lexical lint rules decide most findings from one file's syntax tree. Type-aware rules, whole-program type checking and taint analysis build a model across files, so they cost more by structure rather than by configuration.
  • Cache behaviour. Most linters cache per file and skip unchanged ones. A pipeline that discards the cache between runs pays full price every time, and the fix is usually one line.
  • Job multiplication. A matrix across three versions and two operating systems runs your scan six times. Analysis rarely needs the matrix; tests do.
  • Pull-request volume. The multiplier that grows with headcount, and the reason a cheap-looking scan becomes a line item.

A pattern that keeps the fast feedback and moves the expensive analysis off the critical path:

name: static-analysis

on:
  pull_request:
  schedule:
    - cron: "0 3 * * 1"

jobs:
  quick:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx eslint --cache --cache-location .eslintcache .

  deep:
    if: github.event_name == 'schedule'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx tsc --noEmit

The pull request gets the cheap pass that developers wait on; the expensive whole-program pass runs on a schedule where nobody is watching a spinner. If you need the changed-files variant, drive it from git rather than a plugin:

git diff --name-only --diff-filter=ACMR "origin/${BASE_REF}...HEAD" -- '*.py' \
  | xargs -r ruff check

More on which layer should own which check in linting in CI.

The triage bill, and why the first scan is the expensive one

Point any analyser at a codebase that has never been analysed and it reports everything it has ever been true about — years of accumulated findings arriving in a single afternoon. None of that is the tool malfunctioning. It is simply the invoice for the analysis you were not doing.

There are two ways to pay it, and the choice sets your cost for the entire rollout.

Fix the backlog first. Honest, thorough, and usually fatal. It turns a tooling decision into a project with no visible benefit outside the team, competing directly with shipping. Most rollouts that take this route stall.

Baseline it, then ratchet. Record the existing findings as accepted, gate only new code, and let the backlog shrink as files get touched for other reasons. The cost drops to near zero on day one, the standard applies to everything written from then on, and the debt is paid incrementally by the people already in each file. Many analysers ship a baseline mechanism; check yours before writing your own, and if it has none, restricting the gate to changed files approximates it.

The recurring part of the triage bill is false positives, and its size is set by rule selection rather than by the tool. A scanner producing findings your team consistently dismisses is charging you attention for nothing — which is a configuration problem, not a licensing one. Working through scan noise covers what security scanning genuinely adds and where it reliably over-reports.

Making it cheaper without making it useless

  • Enable rules you will act on. An enabled rule you routinely ignore has a running cost and no benefit.
  • Separate blocking from informational. Block on the small set that means "this is a defect"; report the rest without stopping anyone.
  • Autofix everything mechanical. A finding repaired on save never becomes triage.
  • Exclude generated and vendored code. Unactionable by definition, and on per-line pricing expensive too.
  • Give whole-program analysis its own stage, so type checking never sets the latency of the fast pass.
  • Review the rule set on a schedule. Rule sets accrete; a periodic pass keeps the attention bill flat.

What it costs not to run it

This is where invented figures usually appear, so here is the shape instead of a number. The cost of skipping analysis is not evenly spread: it concentrates in defects that reach production, in review time humans spend on things a machine decides better, and — for security findings — in issues whose cost is set by the incident rather than the bug.

The asymmetry is the argument. A finding caught in the editor costs seconds; caught in review it costs two people's context; in production it costs an incident. You need no benchmark to see which side of that a scan sits on.

FAQ

Is a free linter enough, or do we need a paid scanner?

For style, correctness and maintainability, open-source tooling is genuinely competitive and is what most teams run. Paid products tend to earn their place on security depth, cross-repository reporting, and the governance layer an organisation needs when many teams answer to one policy.

How do per-contributor pricing models actually count?

Usually from commit history over a rolling window, which is why the number can exceed your headcount — old contributors, contractors and automation accounts all show up. Check the vendor's definition against your own repository history before estimating.

Will adding static analysis slow down our pipeline?

Only if you let one job do everything. Modern linters finish fast enough to run on every pull request; type checking and taint analysis are the slow parts, and they belong in their own stage or on a schedule.

What is the cheapest way to add scanning to a legacy codebase?

Baseline the existing findings and gate only new or changed code. It costs almost nothing on day one and applies the standard to everything written from then on, instead of demanding a cleanup project first.

How do we budget for triage time?

Estimate it from findings per pull request rather than from repository size, because that is what people actually read. Run the tool on a handful of recent merged pull requests, count what it would have raised, and multiply by your weekly volume.


The licence is the easy number, and it is rarely the number that decides whether this works. Budget the CI minutes, budget the attention, baseline the history rather than declaring a cleanup project, and keep only the rules your team acts on. When you are ready to shortlist, compare code-quality and security tools on Lintense, where every comparison states its criteria up front — incremental scanning, baseline support and CI behaviour matter far more to your total cost than rule counts do.

Comments are disabled for this article.