Required Status Checks & Branch Protection
Configure branch protection rules so the main branch can only accept changes after required CI checks pass, preventing broken or unreviewed code from landing.
Why
Without branch protection, anyone can push directly to main or merge a PR with failing checks — a single bad push can break production for everyone. Required status checks turn the CI pipeline from a suggestion into a gate: the branch is protected, the merge button is disabled until all required checks pass, and direct pushes are rejected. This is the single most effective control for keeping the main branch always green. Branch protection also enforces code review (required approvals), dismisses stale reviews when new commits are pushed, and prevents force pushes and branch deletion — together these make the main branch an auditable, reviewable, always-buildable line of history. The cost is near zero (a few minutes of setup) and the benefit is preventing the most common cause of broken main branches.
When
As soon as a team forms around a repository (more than one contributor) — branch protection is the baseline control that should never be skipped, and should be tightened as the team grows.
How
1. List the checks that must pass - lint, type-check, unit-test, integration-test, build - Only require checks that are reliable (not flaky) and fast enough to not block the team 2. Create the branch protection rule (GitHub) - Settings > Branches > Branch protection rules > Add rule - Branch name pattern: main - Require status checks to pass: select lint, type-check, unit-test, build - Require branches to be up to date before merging (ensures the check runs on the latest main) - Do NOT require checks that are informational only (e.g. code-coverage-comment) 3. Require pull request reviews - Required approving reviews: 1 (or 2 for critical repos) - Dismiss stale pull request approvals when new commits are pushed - Require review from Code Owners (use CODEOWNERS file for path-based ownership) 4. Restrict direct pushes and force pushes - Restrict who can push to matching branches (no one — all changes via PR) - Do not allow force pushes - Do not allow deletions 5. Verify the gate works - Open a PR that fails a required check — the merge button should be disabled - Try to push directly to main — it should be rejected
Example
# .github/CODEOWNERS — path-based review ownership
* @team-lead
src/data/ @data-team
src/components/ @frontend-team
astro/ @frontend-team
.github/workflows/ @devops-team
# Branch protection (GitHub REST API)
curl -X PUT https://api.github.com/repos/$ORG/$REPO/branches/main/protection \
-H "Authorization: token $TOKEN" \
-d '{
"required_status_checks": {
"strict": true,
"contexts": ["lint", "type-check", "unit-test", "build"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"required_approving_review_count": 1,
"dismiss_stale_reviews": true
},
"restrictions": null,
"allow_force_pushes": false
}'Common Problems
- ⚠Requiring a flaky e2e check as a required check — the e2e test fails randomly, the merge button is disabled, and the developer cannot merge a correct PR; the team loses trust in the gate and starts bypassing it.
- ⚠Requiring an informational check (like a coverage comment bot) that posts a comment but never fails — it adds noise to the required checks list and obscures the checks that actually gate quality.
- ⚠Not enforcing the rule for administrators (enforce_admins: false) — an admin pushes directly to main during a hotfix, bypassing all checks, and introduces a bug that the gate was meant to prevent.
- ⚠Requiring checks that no longer exist (a workflow was renamed or deleted) — GitHub silently ignores the missing context, the gate is weaker than intended, and no one notices until a bug slips through.
Best Practices
- ✓Require only checks that are reliable and fast (lint, type-check, unit-test, build) — keep e2e as a non-required informational check or move it to a nightly job, so flakiness does not block merges.
- ✓Set enforce_admins: true so the rule applies to everyone — admins can still bypass via the API in a documented emergency, but the default path is gated.
- ✓Use a CODEOWNERS file to require path-based review — data files require data-team approval, infra files require devops approval, which routes reviews to the right experts automatically.
- ✓Audit the required checks list quarterly — remove checks for deleted workflows, add checks for new critical workflows, and verify the gate still matches the pipeline.