Reviewing is a habit. Required review is a policy the platform enforces, and the difference is whether a change can reach your default branch when nobody looked.
This lesson is about the enforcement layer: what can be required, what each requirement actually guarantees, and where the gaps are.
What can be required
Section titled “What can be required”Review requirements are configured either through branch protection or through a ruleset. Both express roughly the same options:
| Requirement | What it enforces |
|---|---|
| Required approvals (N) | At least N approving reviews before merge |
| Dismiss stale approvals | Approvals are discarded when new commits are pushed |
| Require review from code owners | Owners of the touched paths must approve |
| Require approval of the most recent push | Someone other than the last pusher must approve |
| Require conversation resolution | All review threads resolved before merge |
| Restrict who can dismiss reviews | Limits dismissal to named actors |
Each closes a different hole, and each has a cost. Turning all of them on maximally is not a security posture; it is a way to make a repository unpleasant enough that people work around it.
The requirements that actually matter
Section titled “The requirements that actually matter”Required approvals
Section titled “Required approvals”The base requirement: N people must approve. One is the common choice; two is common for higher-risk repositories.
What it guarantees is thinner than it appears. It guarantees that N accounts pressed approve — not that anyone read the diff. Required approvals raise the cost of merging unreviewed code; they do not make it impossible.
Dismiss stale approvals
Section titled “Dismiss stale approvals”Without this, the sequence approve → push something entirely different → merge is permitted. The approval refers to a commit that is no longer the head.
With it, any push discards existing approvals. This closes the hole and introduces friction: fixing a typo after two approvals costs both of them.
For most teams this should be on. The friction argument is real, and the correct response to it is smaller pull requests rather than weaker policy.
Require approval of the most recent push
Section titled “Require approval of the most recent push”A subtler protection. Without it, an author can approve a colleague’s pull request, then push their own commits to it and merge — their own code, technically approved.
This requirement demands that the approver be someone other than whoever pushed last. It is the setting that makes “two people saw this” genuinely true, and it is the one most often left off.
Require review from code owners
Section titled “Require review from code owners”Ties approval to the right people rather than any people. Configured through CODEOWNERS, which maps paths to owners.
Worth understanding precisely: CODEOWNERS decides who is asked. This setting decides whether their answer is required. They are two separate decisions and both must be made.
Require conversation resolution
Section titled “Require conversation resolution”Prevents merging with unresolved review threads. Cheap, and it stops the common failure where a substantive question is left unanswered and merged past.
Why approved pull requests still cannot merge
Section titled “Why approved pull requests still cannot merge”The single most common confusion. gh pr view distinguishes the two:
gh pr view PULL_NUMBER --json mergeable,mergeStateStatus,reviewDecisionWhat it doesReports Git mergeability, the policy state, and the review decision as three separate fields.
Why we run itThey answer different questions. A pull request can be cleanly mergeable in Git terms, fully approved, and still blocked by policy — and only mergeStateStatus says so.
Expected resultA JSON object with the three fields, which frequently disagree.
Output:
{ "mergeStateStatus": "BLOCKED", "mergeable": "MERGEABLE", "reviewDecision": "APPROVED"}Common mergeStateStatus values and what they mean:
| Value | Meaning |
|---|---|
CLEAN | Everything satisfied |
BLOCKED | A policy requirement is unmet — reviews, checks, conversations |
BEHIND | Base has moved and up-to-date branches are required |
DIRTY | Merge conflicts |
UNSTABLE | A non-required check is failing |
UNSTABLE is worth knowing: it means something failed but nothing that blocks. Merging into an
UNSTABLE state is legitimate and sometimes correct, and it is also how flaky non-required checks
get ignored into uselessness.
Bypass
Section titled “Bypass”Every enforcement mechanism needs an escape hatch, and every escape hatch is a hole.
Bypass can be granted to repository administrators, to named teams, or to specific Apps. The questions worth answering deliberately:
Do administrators bypass by default? Historically administrators could ignore branch protection unless the rules were explicitly applied to them. Enabling enforcement for administrators is usually correct — the point of the policy is that it applies.
Which Apps need bypass? Automation that merges, or that pushes release commits, often needs it. Grant it to the App, not to a human account it borrows.
Is there a break-glass path? During an incident, someone may need to push a fix without waiting for review. Deciding this in advance, with an audit trail, is far better than discovering it at three in the morning.
What required review does not give you
Section titled “What required review does not give you”Being clear-eyed about this matters, because required review is often treated as a security control.
It does not guarantee anyone read the code. Approval is a button.
It does not stop a malicious insider with approval rights. Two colluding accounts satisfy a two-approval rule.
It does not cover everything. Wikis are a separate repository with no protection. Repository settings changes are not pull requests. Releases can be published without review.
It does not apply to bypassers. See above.
Required review is a control against accident and haste, and it is very good at that. Against a determined adversary it is one layer among several, alongside signed commits, restricted push access, audit logging and required status checks.
A sensible starting configuration
Section titled “A sensible starting configuration”For a team repository where main is deployed:
- Require a pull request before merging
- One approval, or two for anything with production impact
- Dismiss stale approvals on push
- Require approval of the most recent push
- Require conversation resolution
- Require the status checks that genuinely gate quality — no more
- Enforce for administrators
- Code owner review on the paths that have genuine owners
For a solo project, most of this is friction with no benefit. Requiring a pull request and status checks gives you the CI gate and the record without the approval deadlock that self-approval prohibition creates.
Inspecting current policy
Section titled “Inspecting current policy”gh api repos/OWNER/REPO/branches/main/protection \ --jq '{reviews: .required_pull_request_reviews, checks: .required_status_checks.contexts}'gh ruleset list --repo OWNER/REPOgh ruleset check --repo OWNER/REPO maingh ruleset check is genuinely useful — it reports which rules would apply to a given branch,
including rules inherited from organisation-level rulesets that are not visible in the repository’s
own settings.
Rolling out review requirements
Section titled “Rolling out review requirements”Turning on required reviews for the first time on an active repository is disruptive if done carelessly. A sequence that works:
- Announce it, with the reason. “We shipped three incidents from unreviewed pushes last quarter” makes the case; “best practice” does not.
- Require a pull request first, with no approval requirement. People adjust to the workflow change without also being blocked.
- Add one approval a week or two later.
- Add stale dismissal, which is where friction becomes noticeable.
- Add code owner review last, once CODEOWNERS is accurate.
Doing steps 2 through 5 simultaneously produces a repository where nothing merges for a day and everyone concludes the policy is the problem.
Skipping the announcement produces the same outcome plus resentment. A push rejected for a reason nobody explained is experienced as a tooling failure.
Requirements that are not about approvals
Section titled “Requirements that are not about approvals”Review requirements are usually discussed as a number, but the settings around them do more work than the number does.
Required conversation resolution is the cheapest genuine improvement available. It costs almost nothing and prevents the specific failure where a reviewer asks a substantive question, the author pushes an unrelated fix, and it merges with the question unanswered.
Required status checks are usually more valuable than a second approval. A human might miss a broken test; the test does not.
Require branches to be up to date closes the correctness gap where a change passed CI against an older base. On a low-traffic repository it costs nothing. On a busy one it creates the race that merge queues exist to solve — so enable it, and if it becomes painful, that pain is the signal to consider a queue rather than to disable the requirement.
Restrict who can push is worth considering even alongside required pull requests. It constrains who performs the merge, not just who proposes — which matters if your concern is a compromised account rather than a careless colleague.
The solo-maintainer problem
Section titled “The solo-maintainer problem”GitHub does not permit approving your own pull request. On a single-maintainer repository, requiring one approval therefore means nothing can ever merge.
Three workable answers:
Do not require approvals. Require a pull request and status checks instead. You still get the record, the CI gate and the diff review — you simply cannot approve, and you merge on the checks.
Add a bypass for yourself. Honest, and it makes the policy decorative.
Use an App to approve. Legitimate for narrow automated cases such as dependency updates that pass a strict allowlist, and a poor idea generally, since it satisfies a human-review requirement with no human.
The first is usually right. The value on a solo repository is the CI gate and the record, not the approval.
Reading the effective policy
Section titled “Reading the effective policy”When behaviour is confusing, the question is what actually applies — which can come from branch protection, a repository ruleset, an organisation ruleset, or all three.
gh ruleset check --repo OWNER/REPO maingh ruleset list --repo OWNER/REPOgh api repos/OWNER/REPO/branches/main/protection --jq '.required_pull_request_reviews'gh ruleset check is the one to run first: it resolves everything into the effective rules for one
branch, including organisation-level rules that do not appear in the repository’s own settings and
are invisible to a repository administrator looking for them.
The failure mode this prevents: spending an afternoon removing a branch protection setting and finding the behaviour unchanged, because an organisation ruleset was enforcing it all along.
Common mistakes
Section titled “Common mistakes”Requiring reviews on a solo repository. Self-approval is prohibited, so nothing can merge.
Leaving stale dismissal off. Permits approve-then-change.
Not requiring approval of the most recent push. Permits self-merging your own additions.
Treating CODEOWNERS as enforcement. It routes; a separate setting enforces.
A long bypass list. Quietly nullifies the policy.
Requiring every check. Flaky non-essential checks block merges and teach people to bypass.
Assuming required review is a security boundary. It is a control against accidents.
Exercise
Section titled “Exercise”Use a repository where you can add a collaborator, or a second account.
- Enable a rule requiring one approval on
main. - Open a pull request and confirm you cannot approve your own.
- Have the second account approve, then push a new commit as the author.
- Check
gh pr view --json reviewDecision,mergeStateStatus— whether the approval survived tells you how stale dismissal is configured. - Enable stale dismissal and repeat step 3. Observe the difference.
- Run
gh ruleset check --repo OWNER/REPO mainand read what applies.
Reviewing the policy itself
Section titled “Reviewing the policy itself”Governance accumulates. Requirements are added after incidents and rarely removed, so a repository’s policy tends to reflect every problem it has ever had rather than the ones it currently has.
An annual review is worth the half hour. For each requirement, three questions:
What failure does this prevent? If nobody can name one, it is a candidate for removal.
Has it ever prevented that failure? Rule insights, where available, answer this directly. A rule that has never blocked anything is either perfectly deterrent or unnecessary, and the distinction matters.
What does it cost? Latency per change, multiplied by every change. A requirement adding two hours to every merge on a repository merging fifty times a week is a hundred hours a week of waiting.
The honest outcome of such a review is usually that one or two requirements should go and one should be added. A policy that only grows is one nobody is thinking about.
Requirements and repository size
Section titled “Requirements and repository size”What is appropriate scales with how many people can push, not with how important the code is.
| Contributors | Reasonable baseline |
|---|---|
| One | Pull request required, status checks, no approval requirement |
| Two to five | One approval, stale dismissal, conversation resolution |
| Five to twenty | Add code owner review on paths with real owners |
| Twenty or more | Add most-recent-push approval; consider a merge queue |
| Public contributions | All of the above, plus restricted push access |
A solo repository with two required approvals cannot merge at all. A twenty-person repository with no requirements will have unreviewed changes on the default branch within a week. Matching the policy to the situation is more useful than applying the strictest available everywhere — which is what produces the bypass lists that make the policy decorative.
What you learned
Section titled “What you learned”- Required approvals guarantee that buttons were pressed, not that code was read.
- Stale dismissal closes the approve-then-change hole at a real cost in friction.
- Requiring approval of the most recent push is what makes “two people saw this” true.
- CODEOWNERS routes review; a separate setting makes it required.
mergeableandmergeStateStatusanswer different questions.- The bypass list is where enforcement is actually decided.
Start small
Section titled “Start small”If you are adding review requirements to a repository that has none, the highest-value first step is not an approval count. It is requiring a pull request at all, plus the status checks that genuinely gate quality.
That alone gives you the record, the diff, and the CI gate — most of the benefit, with almost none of the friction. Approval requirements are worth adding next, and everything beyond that should be justified by a failure you can name.
Auditing bypass use
Section titled “Auditing bypass use”The bypass list determines what your policy actually enforces, and its use is worth reviewing rather than assuming.
Where rulesets are in force, rule insights record evaluations including bypasses:
gh api "repos/OWNER/REPO/rulesets/rule-suites?per_page=100" --paginate \ --jq '.[] | select(.result == "bypass") | [.pushed_at[0:10], .actor_name, .ref] | @tsv'2026-08-19 release-bot refs/heads/main2026-08-14 alice refs/heads/main2026-08-02 release-bot refs/heads/mainRead that list with two questions. Is the App’s use expected? Release automation pushing to main
is what its bypass exists for, and seeing it confirms the configuration works.
Is the human’s use explained? One entry during a known incident is fine. A pattern of them means the rule is wrong for the work — either it prevents something legitimate, or the emergency path is being used routinely because the normal path is too slow.
Either conclusion is actionable, and neither is visible without looking. A bypass list nobody reviews is a policy nobody is enforcing, and the people subject to it will notice long before the people who configured it do.