Branch protection & required checks
Add branch protection and required checks in GitHub to lock main and enforce CI quality gates. Step-by-step, with troubleshooting.
Focus: add branch protection and required checks
You’ve got a working CI pipeline that runs tests and builds artifacts — but nothing stops anyone from pushing straight to main with a broken test. Without branch protection and required checks, your main branch is a wild west, and a single bad merge can ship production-breaking code. This lesson shows you how to lock down main like a professional team, enforce your CI gates, and turn your pipeline into a real safety net.
The problem this lesson solves
If your team has grown beyond a solo project, you’ve probably seen it happen: a developer pushes directly to main, skips the tests, and the whole deployment breaks. Maybe someone force-pushes over a reviewed PR, or a stale branch merges code that was never checked. The root cause isn’t lack of discipline — it’s the absence of automated guardrails. Branch protection rules are GitHub’s way of enforcing process: who can push, what checks must pass, and what happens when code doesn’t meet the bar.
Without required checks, your CI pipeline is a suggestion, not a gate. Adding branch protection and required checks turns your main branch from a free-for-all into a controlled environment where only verified code gets in. This lesson is the first real step toward professional, blame-free CI/CD — and it directly sets you up for the next lesson on merge strategies and pull request automation.
Core concept / mental model
Think of branch protection as the security guard at the entrance to a building called main. The guard has a list of rules: only authorized people can enter, and they must show a valid ID — in this case, a passing CI check. Without the guard, anyone can walk in. With it, only code that passes your checks and comes from a reviewed PR can merge.
Key terms to know:
- Branch protection rule — a set of conditions applied to a branch (like
main) that restrict direct pushes and enforce review and check requirements. - Required checks — CI status checks (e.g., GitHub Actions jobs) that must succeed before a merge is allowed.
- Required reviews — minimum number of approving reviews before merge.
- Merge queue — a queue that runs checks on the merged result before merging.
Here’s the mental flow: 1. A developer creates a branch. 2. They push code and open a PR. 3. CI runs checks on the PR. 4. If required checks pass and required reviews approve, the PR can merge. 5. If either fails, the merge is blocked.
How it works step by step
GitHub applies branch protection rules when a merge is attempted. Here’s the logical sequence:
- Define the protected branch — usually
main(ormaster). - Set required checks — list the status checks that must pass. Every commit pushed to the branch triggers these checks.
- Set required reviews — choose how many approving reviews the PR needs.
- Additional protections — prevent force pushes, allow only specific users or apps to push, or require a merge queue.
- Merge is blocked unless all conditions are met.
Cause → effect: If a check fails, GitHub shows the PR as “checks failed” and blocks the merge button. If a reviewer hasn’t approved, the button stays gray. Only when every condition is green does the merge become possible.
Hands-on walkthrough
Let’s add branch protection to a GitHub repository and require our CI check. We’ll assume you have a GitHub repo with a workflow that runs tests (e.g., ci.yml).
Step 1: Identify your check name
Open your repo, click Actions, and run the workflow once on a branch. Note the check name — usually the job name, e.g., test or build-and-test.
Step 2: Add branch protection via UI
- Go to Settings → Branches.
- Under Branch protection rules, click Add rule.
- In Branch name pattern, type
main. - Check Require status checks to pass before merging.
- In the search box, type the name of your check (e.g.,
test) and select it. - Check Require branches to be up to date before merging (recommended).
- Also check Require a pull request before merging and set required approvals to 1.
- Save.
Now any direct push to main is rejected, and a PR must pass the test check plus get an approval.
Step 3: Protect via CLI (using GitHub CLI)
If you prefer automation, use the gh CLI:
gh api \
-X PUT /repos/{owner}/{repo}/branches/main/protection \
-H "Accept: application/vnd.github+json" \
-F required_status_checks[strict]=true \
-F required_status_checks[checks][][context]=test \
-F enforce_admins=true \
-F required_pull_request_reviews[required_approving_review_count]=1 \
-F required_linear_history=true
Replace {owner} and {repo} with your values. Note: the -F syntax sends JSON; you might need to adjust quoting for your shell.
Step 4: Test the protection
- Create a new branch, change a file, and try to push directly to
main:
git push origin main
Expected output:
remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Cannot force-push to this protected branch
To github.com:owner/repo.git
! [remote rejected] main -> main (protected branch hook declined)
- Open a PR from your branch. Add a commit that fails the test, and observe the merge button is blocked. Then fix the test, push again, and once checks pass and you approve, merge the PR.
Step 5: Automate the rule via IaC (Terraform)
For larger teams, manage branch protection as code. A minimal Terraform example:
resource "github_branch_protection" "main" {
repository_id = "my-repo"
pattern = "main"
required_status_checks {
strict = true
contexts = ["test"]
}
required_pull_request_reviews {
required_approving_review_count = 1
}
}
Apply with terraform apply and the rule is reproducible everywhere.
Compare options / when to choose what
There are several ways to enforce branch protection. Here’s a comparison:
| Option | Best for | Setup effort | Flexibility |
|---|---|---|---|
| GitHub UI settings | Solo devs, small teams, quick setup | Low | Medium |
| GitHub API / CLI | Scripted environments, CI-driven | Medium | High |
| Terraform / IaC | Multi-repo orgs, compliance, reproducibility | High | Highest |
| GitHub Merge Queue | Large teams, fragile main branch | Medium | High |
When to choose what: - Use the UI for a quick fix in one repo. - Use the API/CLI if you need to automate the same rule across many repos. - Use Terraform if your org treats infrastructure as code and needs auditability. - Use Merge Queue if you often have stale branches and want to test the merged result before merging.
Troubleshooting & edge cases
Symptom: Check not showing in the list of required checks - Cause: The workflow hasn’t run on the branch yet, or the check name is wrong. - Fix: Run the workflow once by pushing a commit to the branch or opening a PR. Confirm the exact job name in the workflow YAML.
Symptom: Direct push still works after enabling protection
- Cause: The branch name pattern doesn’t match main, or the rule is overridden by a higher-priority rule.
- Fix: Check that your pattern is exactly main. If you have wildcard rules, remember GitHub uses the most specific rule — ensure no conflicting rules exist.
Symptom: PR merge button says “Base branch is out of date” - Cause: You enabled “Require branches to be up to date before merging”. - Fix: Merge or rebase the target branch into your PR branch, or use a merge queue to handle it automatically.
Symptom: Force-push rejected even though you’re the owner
- This is expected — protection blocks force-pushes, including for admins (if enforce_admins is on).
- Fix: If you truly need to force-push (e.g., rewriting history on a feature branch), ensure protection applies only to main, not feature branches.
Symptom: Required reviews block your own PR - This is normal. You can’t approve your own PR. Ask a teammate to review, or temporarily lower the required approvals number while iterating (but turn it back up).
What you learned & what's next
You now understand why branch protection is essential, how required checks work, and how to add them using the UI, CLI, and infrastructure-as-code. You can protect main, enforce CI gates, and prevent bad code from merging. This is a huge step toward professional CI/CD.
Next lesson: Merge strategies and pull request automation — you’ll learn how to configure merge queues, auto-merge, and efficient branching strategies that complement your protection rules.
Practice recap
In your own repository, protect main with at least one required check (your CI job) and require one approval. Create a PR, intentionally break the test, see the block, then fix it and merge. Next, try using the GitHub CLI to apply the same rule to a second repository.
Common mistakes
- Enabling branch protection but not enforcing status checks — the merge button stays green until you explicitly require a check context, so make sure you add the exact job name that runs your tests.
- Forgetting to set
enforce_admins— if you leave admin overrides enabled, admins can bypass all rules, which defeats the purpose for large teams. - Using a branch name pattern that doesn't match
mainexactly — if you typemasteror omit the leading slash, the rule won't apply - Not updating the required check list after renaming a CI job — the protection rule holds an old context string, and commits will fail with 'expected' status checks that never appear
Variations
- Instead of GitHub UI, use GitHub CLI to script the protection rule across many repositories with identical settings.
- Use a GitHub Action like
branch-protectionto reapply rules on every push to the repo, keeping protection in sync with your CI pipeline. - For enterprise teams, manage branch protection as code via Terraform (or AWS CodeCommit/ GitLab protected branches) to ensure consistency and auditability.
Real-world use cases
- A healthcare SaaS team requires all merges to
mainto pass a security scan and two approvals before release. - An open-source library uses branch protection to require up-to-date branches so contributors must rebase before merging.
- A fintech platform automates its release pipeline by requiring a
deploy-stagingcheck onrelease/*branches and blocks direct pushes to production branches.
Key takeaways
- Branch protection rules are the first line of defense for your main branch — they block direct pushes and enforce CI checks.
- Required checks only gate merges if you explicitly list the job (context) names; otherwise the CI is just informational.
- You can apply branch protection via the UI, GitHub CLI, or IaC — choose based on the number of repos and compliance needs.
- Troubleshooting relies on verifying the check name, branch pattern, and rule precedence.
- Enabling protection requires also considering admin overrides and merge queue behavior to avoid bypasses.
- This lesson sets up the merge strategy lesson that follows, as protecting main is a prerequisite for safe merges.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.