Continuous Integration
Date: 2026-08-17
Merging everyone’s work into a shared branch frequently enough that conflicts stay small. It’s a practice, not a product — a team running GitHub Actions on three-week branches has bought the tool and skipped the practice.
Continuous integration (CI) is the discipline of integrating work into a shared mainline frequently — at least daily — with automated verification that the mainline still works.
The automation is in service of the practice. The practice is integrating often.
The problem it solves
WITHOUT
4 developers, 3-week branches
↓
each branch diverges
↓
integration at the end
↓
conflicts interact; someone's work
is rewritten; two weeks of
"integration phase"
WITH
everyone merges daily
↓
conflicts are small and immediate
↓
integration stops being an event
Integration pain grows non-linearly with branch age — the conflicts don’t just accumulate, they interact — Branching Strategies.
What “continuous” requires
1 everyone merges to mainline
at least daily
2 every merge triggers an automated
build and test
3 the mainline is ALWAYS releasable
4 a broken build is fixed
immediately, ahead of other work
Point 4 is the cultural one and the one that decides whether it works. A red build tolerated for a day means nobody trusts the signal, and everyone starts merging on top of broken code.
Point 3 is what makes incomplete work need feature flags — you can’t hold a feature back on a branch if branches don’t live long enough — Feature Flags.
The pipeline
push
│ install (cached)
│ lint fast, fails early
│ typecheck fast
│ unit tests fast
│ build
│ integration slower
│ e2e slowest
▼
mergeable
Order by speed, not by importance. Cheap checks failing in twenty seconds beat waiting eight minutes to learn about a lint error — Pipeline Design.
Speed is the constraint
< 10 min people wait for it
← the target
10–20 min people context-switch
→ they lose the thread
> 20 min people stop waiting
→ merge before it finishes
→ the gate is decorative
A slow pipeline is not a minor inconvenience — it changes behaviour. Once people stop waiting, CI has become a thing that occasionally emails you rather than a gate.
The levers, in order: caching, parallelism, running only affected tests, and moving the slowest checks off the blocking path — Build Caching.
Trust is the other constraint
FLAKY TESTS
→ re-run until green
→ a real failure gets re-run too
→ the gate is gone
— Flaky Tests
TOLERATED RED MAIN
→ people merge on top of broken
→ the next failure is ambiguous
See: Flaky Tests
A pipeline people don’t trust is worse than none, because it costs time and provides no assurance. Quarantine flaky tests immediately rather than tolerating them.
What belongs in it
BLOCKING
lint, format check
type check — Type Checking in CI
unit + integration tests
build succeeds
critical E2E journeys
secret scanning — Secrets Management
NON-BLOCKING, REPORTED
bundle size trend
coverage trend
full accessibility scan
dependency audit
visual diffs for review
See: Type Checking in CI · Secrets Management
The distinction matters. Everything blocking must be fast and deterministic; everything else informs without stopping work — and putting a slow non-deterministic check in the blocking set is how pipelines become untrusted.
CI is not CD
CI integrate frequently, verify
automatically
→ mainline always works
CD deploy automatically once it does
→ Continuous Deployment
CI is the prerequisite. Deploying automatically from a mainline nobody trusts is just shipping bugs faster.
Where teams get it wrong
- Long branches with a CI badge. The tool without the practice
- Running only on pull requests, not on the mainline. Then
maincan be broken by a merge and nobody knows — which also breaks bisect — Git Bisect - Tolerating red. The single most damaging habit here
- No local equivalent. If developers can’t run the same checks before pushing, CI becomes the place mistakes are discovered — Pre-Commit Hooks
- Skipping checks under deadline. Once, understandably. Twice, it’s the new normal