Branch protection cost in 2026: when required checks multiply minutes
Branch protection is the right default. Required status checks are how teams enforce quality before code lands on main. The cost is invisible until a quarterly bill review reveals that 60% of CI compute is going to checks that the change being pushed could not possibly affect. This page covers the cost shape of required-check policies in 2026, the path-filter and selective-CI patterns that cut the bill without losing safety, and the right way to design a check set that is both rigorous and frugal.
The arithmetic of required checks
6 required checks x 6 pushes per PR x 80 PRs/week = 2,880 check runs/week. At 4 minutes average per check, 11,520 CI minutes/week, or 46,000/month. At GitHub Actions Linux $0.006/min, $276/month for the required-check suite alone. Add the rounding tax and the monthly figure usually lands $400-700. Path filtering typically halves it.
The check-multiplication problem
Every PR push triggers every required check. A PR receives multiple pushes during its lifetime: the initial push, push after review feedback, push to resolve a conflict, push for final cleanup. Four to eight pushes per PR is typical for a working team. Each push runs the full required-check suite. The cost scales as required_checks x pushes_per_PR x PRs_per_week.
On a typical scaleup team: 6 required checks (lint, unit tests, integration tests, build, security scan, type check), 6 pushes per PR, 80 PRs per week. That is 2,880 check executions per week, or 11,520 per month. Average 4 minutes per check (a mix of fast lint at 30 seconds and slow integration at 8 minutes), and the monthly minutes consumed by required-checks alone is 46,000.
At $0.006/min Linux on GitHub Actions, that is $276/month for the required-check suite. Add the rounding tax (each check rounds up to the next minute) and the actual bill is often $350-450. For larger teams (100+ devs, 300+ PRs/week) the figure scales to several thousand per month for the required-check suite alone.
The path-filter pattern
The simplest cost reduction: only run a check if files it depends on have changed. A frontend lint check should not run on a PR that only touches backend Go files. A backend integration test should not run on a PR that only touches CSS.
GitHub Actions supports path filtering at the workflow level (paths: on a push or pull_request trigger) and at the job level (via the dorny/paths-filter action, which sets outputs that downstream jobs can gate on). GitLab CI supports it via rules:changes per job. CircleCI uses dynamic config with the path-filter orb. The mechanics differ; the principle is identical.
One subtle gotcha with path filtering: required status checks must always "report a conclusion" even when they do not run, or the PR sits unmergeable forever. The pattern that works on GitHub: a path-filtered check runs and either does the work or writes a synthetic "skipped" status. GitHub Actions provides the if: false dummy job pattern for this; CircleCI's dynamic config builds the workflow conditionally. Get this wrong and developers will be stuck waiting for never-running checks; this is the most common failure mode of the first path-filter implementation.
Affected-only test selection
Path filtering decides whether a check runs at all. Affected-only test selection decides which subset of tests within a check actually execute. Tools like Nx, Turborepo, Bazel, and Pants compute a dependency graph and identify which tests transitively depend on the changed files. Run only those tests; skip the rest.
Affected-only is the next-level lever beyond path filtering. Path filtering says "skip the backend integration check on a CSS-only PR". Affected-only says "run only the 12 unit tests that depend on the function this PR changed, not the full 4,000-test suite". The savings on monorepos with deep test suites are substantial: 80-95% reduction in per-PR test-execution minutes is typical once dependency-graph-based selection is mature.
The trade-off is correctness risk. If the dependency graph misses a dependency (a test that secretly depends on a file the graph does not know about), affected-only will skip a test that would have caught a real bug. The mitigation is a nightly "run all tests" job that catches the rare misses, plus a discipline of fixing graph gaps when they cause an escape. Most teams accept the trade because the savings are large and the misses are rare.
Cancelling superseded runs
When a developer pushes twice in quick succession, the first run is usually obsolete. Without configuration, both runs execute to completion, doubling the spend on the wasted first run. The fix is workflow-level concurrency cancellation: configure the workflow so that a new push to the same branch cancels any in-progress run.
GitHub Actions: concurrency: { group: pr-{ref}, cancel-in-progress: true }. GitLab: the interruptible: true flag. CircleCI: auto-cancel-redundant-workflows in project settings. The savings depend on how often developers push in rapid succession; for active teams the saving is 10-25% of total CI minutes for almost zero implementation effort.
Designing the right required-check set
Three rules. First, required checks should be high-signal: red means real broken, not flake. Move flaky or noisy checks to optional. Second, required checks should be fast: under 10 minutes total for the slowest required check. If a check is slow, optimise it or split it: the slow part can be optional or nightly. Third, required checks should be path-filtered: every check should declare which paths are relevant to it.
A typical good set for a multi-service repo: lint (30s, on changed code), unit tests (3-5 min, on changed services), build verification (2-4 min, on services with changes), type check (1-2 min, on changed code). Four required checks, all path-filtered, all under 5 minutes individually. Average PR completes required CI in 6-8 minutes. The slow integration tests, the security scans, the visual regression tests live as nightly jobs or as "run on demand" comments rather than blocking the PR.
Reviewing required-check sets quarterly
Required-check sets accumulate. A check added during a 2024 incident response that has not caught a real bug in 18 months is dead weight. Review the set quarterly with one question per check: in the last 90 days, has this check caught at least one real issue? If no, demote to optional. If still no after another quarter, delete.
Make the review a calendar event with the platform-engineering or infra team owning it. Without the explicit ownership, required-check sets only grow. The cost discipline is structural, not technical.
Frequently Asked Questions
What is branch protection?
Branch protection is a set of rules that prevent direct pushes to specific branches and require certain status checks to pass before a pull request can be merged. On GitHub, branch protection rules let you require status checks (CI jobs) to pass, require code review approval, and enforce a linear history. The cost dimension comes from the required status checks: each check is a CI job that must run on every PR push, and the bill scales with the number of required checks times the number of pushes.
How do required checks multiply CI cost?
If you require N checks and a PR receives M pushes during its review, you incur N x M check runs. A typical PR receives 4-8 pushes (initial, review feedback, conflict resolution, final cleanup). With 6 required checks, that is 24-48 check runs per PR. For a 25-developer team merging 80 PRs/week, this multiplies into thousands of check runs per week. The fix is selective CI: each check should only run when files relevant to that check have changed.
How do I implement path filtering in CI?
Each major CI vendor supports it. GitHub Actions: workflow-level paths filter or job-level dorny/paths-filter action. GitLab CI: rules:changes per job. CircleCI: dynamic config with path-filter orb. Bitbucket Pipelines: changesets. The pattern is the same: declare which files trigger which checks, skip the check if no relevant files changed. Done well, this cuts CI minutes per PR by 60-90% on monorepos and by 30-50% on multi-repo setups.
What is the difference between required and optional checks?
Required checks block PR merging until they pass. Optional checks run alongside but do not block. The cost is similar (both consume CI minutes), but required checks have an additional drag: developers wait for them, retry them, investigate failures urgently. Move slow or rarely-actionable checks to optional or move them to a nightly cron rather than per-PR. The remaining required suite should be fast (under 10 minutes) and high-signal (actionable when red).
Should every team use branch protection?
Yes for any team larger than one developer or any project shipping to users. The protection prevents accidental main-branch breakage and enforces the review and CI checks that protect quality. The question is not whether to use branch protection but how to design the required-check set: minimal, fast, high-signal. The wrong implementation is 15 required checks that always pass; the right one is 3-5 checks that consistently catch real problems.