Home Features For Vibe Coders For Developers Pricing Documentation Services Contact
← Back to blog

Why I run scans locally instead of in CI

The standard advice is: put your linter, your tests, your security scans in CI. The CI server is the source of truth. Your local machine is a development environment.

I disagree. Local-first is better for everything except the build itself. Here’s why.

CI is too late

By the time CI runs, you’ve already pushed. You’re already in PR mode. The mental cost of going back and fixing something that ran in CI is higher than the cost of running it locally before you commit.

This isn’t a small effect. I’ve watched teams produce three-commit chains: “fix lint”, “fix typecheck”, “actually fix it”, because every iteration of the same fix has to go through the CI loop. Each iteration is a 4-minute wait, a context switch, and a notification. Multiply that by every dev on a team for a year and you’ve burned weeks.

Run it locally. Pre-commit hooks. Watch mode. Whatever. The faster the feedback, the cheaper the bug.

CI hides the actual problem

When a check fails in CI, you get a log. The log scrolls past. You look for the error. The error is two lines of stack trace and an exit code. You re-run locally to debug, because the CI environment isn’t interactive.

You re-run locally. Why didn’t you start there?

CI’s job is to be the source of truth, not the development environment. If your dev workflow is “push, wait, debug from logs”, you’ve inverted the workflow.

Local is faster

Modern hardware is fast. Your laptop has more RAM than the CI runner. Your local network is a loopback interface. Your filesystem isn’t an overlay over a Docker volume.

A test suite that takes 90 seconds in CI takes 12 seconds locally. A type check that takes 45 seconds in CI takes 6 seconds locally. The CI runner is slower because it’s cold, sandboxed, and shared. Your laptop is warm, dedicated, and has 64GB of RAM doing nothing.

Use it.

CI is for confirmation

The right framing: CI exists to confirm that what you tested locally also passes in a clean environment. Not to be the place where you discover failures.

A green CI build should never be a surprise. It should be the predictable result of “I ran everything locally and it passed.”

If your team has a culture of “let CI catch it”, you’re paying for slow feedback as a substitute for discipline. The fix is hooks, watch mode, and a developer experience that makes local checks fast and obvious.

The exception

There’s exactly one thing CI is the right place for: integration tests against shared services that you don’t want running on every developer’s laptop. A Postgres container, a Redis instance, a mock S3. These are heavy, they take seconds to spin up, and most developers don’t need them on every commit.

Everything else, linting, type checking, unit tests, security scanning, dependency auditing, belongs locally first. CI confirms.

What this looks like in practice

The setup I recommend:

  • Pre-commit hooks: format check, lint staged, type check, unit tests for changed files
  • Pre-push hooks: full test suite, dependency audit, security scan
  • CI: same as pre-push, plus integration tests, plus deploy

The pre-commit is the strictest because the feedback is fastest. The pre-push is the safety net. CI is the receipt.

If your workflow is the inverse, you’re paying the wait tax on every iteration. Move it left. Your laptop can take it.

Join the feed

Get feature releases and engineering updates delivered to your inbox.