Master

Keeping main unbreakable with branch protection

Branch protection is what stops the main branch from being damaged, even by mistake. You set a few rules and GitHub enforces them automatically: reviews required, tests must pass, direct pushes blocked. It's how teams protect their most important branch without having to watch it all the time.

What you will see

{"enabled": true, "block_direct": true, "require_pr": true, "blocking": true}

About 2 min of reading

Try It

I had once merged a broken build straight to main by accident, and it was ugly to undo. Let me set the rules that stop main from being damaged even by mistake, require reviews, make tests pass before any merge, and block direct pushes so every change is forced through a pull request.

1

Start by checking what is already protecting main. A brand-new repo usually has no protection, so main can be pushed to directly by anyone. Seeing the current state before changing it is how you avoid protecting something that was already safe, or leaving something exposed.

gh api repos/ada/myapp/branches/main --jq .protection

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{"enabled": false}
If enabled is false, main is wide open, anyone with write access can push straight to it. That is the state worth fixing before anything else.
2

Turn on branch protection for main, and require at least one approval before any merge. With this on, no one can push bad code straight to main; the rules force review and testing before anything merges, no matter how many people work on the project.

gh api -X POST repos/ada/myapp/branches/main/protection

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{"required_approvals": 1, "enforces_check": true, "block_direct": true}
Set this on the flagship branch at minimum. The branches that hold real production data deserve the strongest rules you can give them.
3

Require status checks to pass before the merge goes through. A status check is whether the pipeline passed, a build, lint, and test run. A red build can never reach main, which means everyone waits for the checks to turn green and the version that ships stays healthy.

gh api -X PUT repos/ada/myapp/branches/main/protection/required_status_checks --input -

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{ "checks": ["ci/build", "ci/lint", "ci/test"], "blocking": true }
If a check is required but does not exist in your workflow, every merge stalls waiting for a test that never runs. Wire the check to a real CI job before you require it.
4

Block direct pushes so every change is forced through a pull request, and forbid force-push, which can wipe history. Restrict direct write to trusted maintainers; everyone else must open a PR. Even admins are not exempt, which stops an accidental force-push from destroying the timeline.

gh api -X PUT repos/ada/myapp/branches/main/protection/push --input -

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{ "enforce_admins": true, "allow_force_push": false, "require_pr": true }
Forcing everyone through a PR is the point, not a nuisance. Without it, the strongest protection is only a suggestion, and a single bad merge is enough to break main.
5

Confirm the rules are actually in place. Protected status now means direct push is blocked, approvals are required, and tests must pass before anything merges. main can only change when the checks turn green, which keeps every version shippable.

gh api repos/ada/myapp/branches/main --jq .protection

Before you run it, say out loud what it will print. Then open the answer.

What it prints
{"enabled": true, "block_direct": true, "require_pr": true, "blocking": true}
If it still shows enabled:false after you set these, the rules did not take. Re-check each one, because a partially applied rule protects only part of the risk.

Data Flow

Rules block direct pushes; every merge needs approval and passing tests.
1 Block direct push
↓
2 Require reviews
↓
3 Require status checks
↓
4 Allow the merge
Why this works
What to protect

Protect main at minimum. Require reviews and passing tests, and block direct pushes so all work is forced through a PR. The branches that hold real data deserve the strongest rules.

Link the checks

Connect your CI to the protected branch. main can only merge when tests pass, which keeps every version shippable and gives everyone a clear signal that something's wrong.

Examples

The rules that protect main

Branch protection on main:
- Require 2 approvals
- Tests must pass
- No direct commits

With these on, no one can push bad code straight to main. The rules force both review and testing before anything merges, which is exactly how a team keeps its flagship branch in a shippable state no matter how many people work on it.

Requiring status checks

Required status checks:
- CI (build + test)
- Lint
- Coverage report

A status check reports whether the pipeline passed, and you can block merges until it turns green. A red build can never reach main, which means everyone waits for the checks, and the version that ships stays healthy as a result.

Limiting who can push

Push restrictions:
- Direct push: maintainers only
- Everyone else: a PR is required
- No force-push

You can restrict direct write access to trusted maintainers. Everyone else must go through a PR, so every change is reviewed. Even admins aren't exempt, which stops an accidental force-push from wiping history.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Giving feedback people actually respond to
Previous Lesson
Giving feedback people actually respond to
Next Lesson
Shipping a version and telling people about it
Shipping a version and telling people about it →