Skip to content

Twelve CI Gates: Which I Put First, and Why in That Order

Lint and format catch cheap mistakes. The gates deciding if code survives sit further down the pipeline, in an order that took years to get right.

· · 7 min read
A dark server rack with status lights, the kind of infrastructure a CI pipeline runs its gates against

The order that took years to get wrong first

Twelve gates. That is roughly what a serious pipeline runs before code merges, once you count format, lint, types, static analysis, three flavors of tests, and four separate security checks. Most teams don’t design that order on purpose. They add a gate whenever something breaks in production, bolt it onto the end of whatever CI script already exists, and never touch the sequence again. I’ve watched pipelines grow that way for years, and the ordering that comes out of it almost always optimizes for the wrong thing: getting to green, not getting to correct.

The rule I actually use, after enough painful reordering to trust it: run the cheapest checks first, but don’t confuse cheap with meaningful. A format check is nearly free and nearly worthless as a signal. Mutation testing is expensive and tells you more about a codebase than almost anything else on this list. The gates that decide whether code survives sit at the far end of the pipeline, not the front. That has real consequences for anyone leaning on an AI assistant to write code, because a model can silence a measurement a lot faster than it can fix what the measurement caught. Why does that keep happening on team after team? Because silencing a warning is a five-minute fix, and actually addressing what it found rarely is.

Gates one through five: cheap enough to run on every commit

1. Format check (Prettier or Biome). Runs in under a second and catches exactly one class of problem: inconsistent whitespace and quote style. It has zero opinion on whether the code works. I still put it first, because a failing format check is the cheapest possible signal that something is off in a commit, and there’s no reason to burn CI minutes on anything else until it passes.

2. Secret scanning (Gitleaks, TruffleHog). Here’s my one genuinely disputable position on this list. Most teams file secret scanning under a “security phase” bolted onto the end of the pipeline, after lint, after tests, sometimes after a manual review. That’s backwards. A leaked API key or a committed .env file isn’t a code-quality problem waiting its turn. It’s an active incident the moment it lands on a shared branch. I run it second, immediately after format, and teams I’ve worked with have saved themselves an actual key rotation by catching it here instead of after merge.

3. Lint (ESLint or Biome). Third, and this is where the AI part of the story turns uncomfortable. An assistant can satisfy a linter in one pass, every time, without writing code that is actually correct. Wrap the unused variable in an underscore. Disable the rule inline. Rename a function to dodge a naming convention. None of that touches whether the logic works. ESLint’s own documentation is honest about this scope: it checks patterns, not behavior. Why do so many teams still treat a clean lint run as proof of quality? I think it’s because green is easy to read, and correctness isn’t. The same teams tend to be the ones that suppress rules instead of fixing code.

4. Type check (tsc --noEmit, mypy --strict). Types catch a real category of bug, the wrong shape passed to the wrong function. But they are gameable too, just with a different tool. any in TypeScript and # type: ignore in Python are the type checker’s version of a lint-disable comment. I once watched a “finished” strict-mypy migration turn out to be several hundred ignores added in an afternoon, with none of the underlying type confusion actually resolved. Was that migration really finished, or just declared finished? The check still catches plenty on the way there, which is why it runs fourth instead of getting skipped.

5. Dependency and SCA scan (OSV-Scanner, npm audit). Fast, because it is a lookup against a known-vulnerability database rather than an analysis of your code. Google’s OSV-Scanner pulls from the same open advisory data most SCA tools use, and running the check this early means a newly disclosed CVE in a transitive dependency blocks the build before anyone wastes an hour debugging something unrelated.

A terminal window mid build, scrolling output in red and white text
Photo by Lukas on Unsplash

Gates six through nine: where cost starts buying something real

6. SAST (Semgrep). Static application security testing looks for exploitable patterns: SQL built from string concatenation, an unvalidated redirect, a hardcoded crypto key. Semgrep’s documentation covers custom rule writing, which matters, because the default ruleset misses plenty that is specific to your own stack. The AI failure mode here is different from lint. Ask a model to “fix the Semgrep finding” and it will often silence the finding with an inline suppression comment rather than change the vulnerable pattern underneath. Read the diff. Don’t trust the green checkmark alone.

7. License and compliance check. The least glamorous gate on the list, and the one most teams skip until legal asks about it. It sits here, not at the end, because a GPL-licensed dependency pulled in transitively is far cheaper to catch and swap before six other pull requests build on top of it.

Close-up of a computer screen showing a code review comment thread
Photo by Yancy Min on Unsplash

8. Unit tests (vitest, pytest). Fast, and the gate almost everyone assumes is the strongest signal on the list. It isn’t, not on its own. A generated test that asserts expect(result).toBeDefined() passes every single time and proves nothing about the code it is supposedly guarding. I would rather see three real assertions than thirty of those. This is also exactly where coverage stops meaning what people think, and it is the strongest argument for gate twelve.

9. Static analysis (SonarQube, SonarCloud). Slower than the earlier gates, because it needs a fuller picture of the codebase rather than a single changed file. SonarSource’s own documentation is explicit that the useful number here is debt trending over time, not a single scan’s pass or fail. I care less about today’s score than whether next month’s is worse. Is the trend line actually moving, or just holding steady on a treadmill of suppressed rules? That number tells you whether a team is fixing things or routing around the tool.

Gates ten through twelve: slow, and the most decisive on the list

10. Integration tests. These exercise the real seams: the database call, the API contract, the queue that a unit test mocks away entirely. Slower, because they touch real infrastructure instead of a stub. But a green integration suite means something a mocked unit test simply can’t claim on its own.

11. Flaky-test detection (N reruns). Running the suite three or five times back to back, specifically to catch nondeterminism, is expensive by design. You’re multiplying total test time just to find the handful of tests that pass on luck rather than logic. Worth every extra minute. A flaky test that gets ignored long enough becomes a test nobody trusts, and an untrusted test is worse than no test at all, because it still shows up green.

12. Mutation testing (Stryker, mut.py). Last, and by far the most expensive gate, because it reruns your entire test suite against dozens or hundreds of deliberately broken versions of your own code. If the suite still passes after a mutation flips a single comparison operator, that suite was never testing the thing it claimed to test. This is the gate that actually answers the question coverage only pretends to answer. It runs dead last because nothing before it is cheap enough to justify going first, and nothing after it needs to run at all.

Twelve gates, four of which an assistant can satisfy without fixing anything underneath, is a survivable ratio if the other eight actually hold the line. So which order do your gates run in right now? Mine took years to settle, mostly by getting it wrong first and paying for it, and I would bet at least one of the twelve on your list is sitting in the wrong slot in your pipeline this week.