Two-step CI
Most teams already run CI on pull requests. A merge queue adds a second validation step that only runs once the PR enters the queue, against the actual state main will have at merge time.
flowchart LR
subgraph PR_phase ["PR phase (cheap, fast)"]
A["PR opened"] --> B["PR CI<br/>lint + unit tests"]
B --> C["Review & approve"]
end
subgraph Queue_phase ["Queue phase (full, expensive)"]
C --> D["Enter queue"]
D --> E["Queue CI<br/>full suite + E2E"]
E --> F["Merge ✓"]
end
style B fill:#F2F4F7,stroke:#d4d6d9,color:#1A1D24
style E fill:#E6F8F2,stroke:#1CB893,color:#1A1D24
style F fill:#E6F8F2,stroke:#1CB893,color:#1A1D24
Cheap checks on every PR. The expensive suite runs only when a PR is about to land.
Why two steps?
PR CI catches the obvious things fast. Lint errors, broken unit tests, type mismatches. The developer gets feedback within minutes and can iterate.
Queue CI answers a different question: will this change still work when it actually lands on main, alongside everything else queued ahead of it? That answer needs the full suite. Integration tests, end-to-end browser flows, infrastructure spin-up, the works.
Running both on every PR wastes CI time and money. Skipping the second step means semantic conflicts slip through. Two-step CI splits the work so each step does what it is good at.
Common configurations
| PR CI | Queue CI | Why this works |
|---|---|---|
| Unit tests only | Full suite + E2E | Fast PR feedback, thorough merge validation |
| Lint + type check | All tests + E2E | Catch formatting issues early, integration last |
| Affected tests only | Full suite | Scale PR CI for monorepos |
| Same as queue | Same as PR | Simple setup when CI is fast and cheap |
Catching what PR CI misses
Take two PRs that both pass PR CI on their own. PR #1 adds a required parameter to a shared API endpoint. PR #2 calls that endpoint without the new parameter. Each PR was tested against an older main where neither change existed. Both go green.
When PR #1 merges first, PR #2 is now broken. Queue CI catches that the moment PR #2 enters the queue, because it runs against main + #1. The test fails before reaching main, the PR is removed, and the author gets a notification with what changed.
This is the kind of semantic conflict that no amount of PR CI can prevent. PR CI tells the developer "your change works in isolation." Queue CI answers "your change works in the world it is about to enter."
CI cost math
Two-step CI is also a cost optimization. The expensive checks (browser tests, load tests, full integration spin-up) only run on PRs that are actually about to merge.
Take a team with 50 PRs per week where 30 pass review and reach the queue:
- Without two-step: 50 full CI runs per week.
- With two-step: 50 lightweight PR CI runs + 30 full queue CI runs.
The savings compound when full CI involves spinning up databases, browser farms, or staging environments. For teams paying for CI by the minute, this can be a real line item.
Related features
- Batching: combine queue CI runs for even more efficiency.
- Speculative checks: run queue CI for multiple PRs in parallel.
See two-step CI in production.
Mergify runs queue CI as a separate workflow on a queue branch. Your existing PR CI keeps running unchanged.