Enforcing Code Review on Bitbucket Cloud with Branch Permissions and Merge Checks
How to combine Bitbucket Cloud branch permissions and merge checks so every change to main passes review and CI — with a worked configuration, the stale-approval trap, and verification steps.
15 Jun 2026, 20:51 UTC

The useful answer first
If you want every change to main to go through review and CI before it lands, Bitbucket Cloud gives you two cooperating mechanisms: branch permissions (who can push, force-push, or delete a branch) and merge checks (conditions a pull request must satisfy before the Merge button works). The practical configuration most teams end up with is: block all direct pushes to main, require two approvals, require at least one successful build, and block merges with unresolved tasks. Everything below shows how that works, why the pieces interact the way they do, and where the setup commonly goes wrong.
Scope note: this covers Bitbucket Cloud. Bitbucket Data Center/Server has similar concepts but different names, plan gating, and check options — do not assume parity.
How the two mechanisms fit together
Branch permissions live under Repository settings → Branch permissions (workspace admins can also set them at the workspace level so they apply to every repo). A permission rule targets a branch name or a glob-style pattern such as main or release/*, and can:
- Restrict who can push to matching branches (down to specific users or groups, or nobody).
- Forbid force pushes and branch deletion.
- Attach merge checks that gate pull requests targeting the branch.
Merge checks are evaluated at merge time in the UI and API. The available checks include a minimum number of approvals, a minimum number of successful builds, no unresolved pull request tasks, and resetting approvals when the source branch changes. Some checks are gated to paid plans, and that gating has changed over time — verify what your tier includes before designing around a specific check (Workspace settings and Atlassian's plan comparison page are the places to look).
A worked configuration for main
Suppose your repo builds with Bitbucket Pipelines and you want a two-reviewer gate. Create a branch permission with these settings:
- Branch pattern:
main - Push access: nobody (this forces all changes through pull requests)
- Allow force pushes: off; allow deletion: off
- Merge checks: minimum 2 approvals; minimum 1 successful build; no unresolved tasks; reset approvals on source-branch change (if your plan includes it)
The developer flow becomes: push a feature branch, open a PR targeting main, Pipelines runs and posts a build status to the PR's head commit, two teammates approve, tasks are resolved, and only then does Merge become available.
A minimal bitbucket-pipelines.yml that satisfies the build check looks like this:
pipelines:
pull-requests:
'**':
- step:
name: Build and test
script:
- npm ci
- npm test
The key detail is the pull-requests trigger: it runs the pipeline against the PR's source commit and posts the result as a commit status. The "successful builds" merge check reads commit statuses on the latest commit of the source branch — which means any CI system that posts build statuses (Jenkins, GitHub Actions via a status-posting step, etc.) can satisfy it, not just Pipelines. If you use external CI, confirm it actually posts statuses back to Bitbucket commits; a green build in your CI dashboard that never reports to Bitbucket will not unblock the merge.
Why "reset approvals on change" matters
Without this check, an approval sticks to the PR even after the author pushes new commits. That means someone can get two approvals on a clean diff, push a malicious or broken fixup, and merge with the stale approvals intact. Resetting approvals on source-branch change closes that hole: any new push invalidates existing approvals and the count starts over.
The trade-off is friction. Teams that iterate with many small fixup commits will re-request reviews constantly. Common mitigations: keep PRs small, use squash merges so history stays clean regardless of fixup noise, and ask reviewers to re-approve quickly on trivial changes rather than treating re-review as a full pass.
Limits worth knowing before you rely on this
- Green build ≠ safe merge. A successful build status only proves that the checked commit passed CI. If
mainhas moved since the PR branch was created, semantic conflicts (two changes that each pass alone but break together) can still slip in. Bitbucket Cloud's merge checks do not include a strict "branch must be up to date" requirement the way some platforms do; if you need that guarantee, enforce it culturally or via a CI step that rebases and re-runs. - Admins may bypass. Users with admin-level repository access can bypass some restrictions unless the relevant "allow admins to bypass" style options are disabled where offered. Audit who has admin access; a permission model is only as strong as its smallest exception list.
- Checks gate the merge, not the push. Merge checks apply to pull requests. If any direct-push path exists (a user or group left in the push list, an overlapping pattern that grants access), the checks are irrelevant for that path.
Common misconfigurations
Overlapping patterns. Rules are evaluated per branch name or pattern, and multiple rules can match one branch. If main has one rule and m* has another, the effective permissions can surprise you. Decide your naming conventions first (main, release/*, hotfix/*), write one rule per pattern, and avoid catch-all patterns that overlap protected names.
Build check pointing at the wrong pipeline. If your pipeline only triggers on branch pushes and not on pull requests, the PR head commit may never receive a status, and the merge stays blocked (or, worse, you loosen the check to compensate). Verify the trigger configuration before blaming the merge check.
Designing around a check your plan doesn't include. Approval reset and some other checks have historically been Premium-gated, and gating has shifted. Confirm your tier first.
How to verify the setup actually works
Do this in a test repository before rolling out to a real one:
- Create the branch permission on
mainas above, then attempt a direct push from a non-admin account:git push origin main. Expect a rejection from the remote. - Open a PR with no approvals and confirm the Merge button is blocked with a message naming the failing checks.
- Push a commit whose CI fails (or have your CI post a failed status to the PR head commit) and confirm the successful-builds check blocks merge until a green status arrives.
- Approve the PR, push a new commit, and confirm approvals reset (if that check is enabled on your plan).
If all four behave as expected, the gate is real and not just configured.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.