Designing Bitbucket Merge Checks: Required Approvals and Pipeline Status as a Merge Gate
An architecture note on Bitbucket Merge Checks: requirements, the smallest enforceable design (two approvals plus a green pipeline), trust boundaries, verification steps, and the failure modes that should trigger a redesign.
16 Jul 2025, 21:42 UTC

Teams that merge straight into main without enforced review eventually ship something nobody read and nothing tested. The fix is not a policy document — it is a mechanical gate. Bitbucket's built-in Merge Checks feature can block the merge button until a minimum number of approvals exist and the associated pipeline reports success. This note covers the requirements, the smallest design that satisfies them, where trust boundaries sit, and how to verify the gate actually works.
Requirements
The gate must satisfy three things:
- No pull request targeting
mainmerges without at least two human approvals. - No pull request merges while its CI pipeline is failing, pending, or missing.
- Every allow/deny decision is auditable after the fact.
Everything else — branch naming rules, commit message linting, deployment gates — is out of scope for the smallest design. Each extra rule adds friction and another failure mode, so add them only when a concrete incident justifies it.
The smallest suitable design
Merge Checks are configured per repository (or per project in Bitbucket Data Center) under the repository settings, in the merge checks or branch permissions area depending on your Bitbucket version. The configuration for this design is:
- Target branch:
main(and any long-lived release branches). - Minimum approvals: 2.
- Prevent merge with unresolved merge checks / require all builds to pass: enabled, wired to the Bitbucket Pipelines build status.
- Optionally, "reset approvals on new commits" if your risk model requires that a post-review push invalidates prior approvals. This is the setting teams most often regret skipping.
No plugins, webhooks, or external services are needed. The pipeline side only requires that your bitbucket-pipelines.yml runs on pull requests, for example:
pipelines:
pull-requests:
'**':
- step:
name: build-and-test
script:
- npm ci
- npm testRun this in the repository root; it requires no special permissions beyond a normal commit. The pipeline result is posted back to the pull request as a build status automatically by Pipelines.
Trust and data boundaries
The entire check executes inside Bitbucket's trust boundary. Approvals are stored as repository metadata, and pipeline results flow from the Pipelines service to the pull request through authenticated internal APIs. Nothing crosses to a third party, which matters for two reasons:
- There is no external availability dependency beyond Bitbucket itself. If you instead enforced the gate with a self-hosted bot reacting to webhooks, that bot's downtime becomes a merge outage or, worse, a silently open gate.
- Auditability is native. Bitbucket records check evaluation and merge decisions in the repository audit log, so "why was this allowed?" is answerable without correlating external systems.
The one boundary to respect is permission scope: only repository admins can change merge check rules. Treat rule changes like code changes — review them, and test them in a staging repository first, because a misconfigured rule applies instantly to every open pull request targeting the branch.
Operational checks
After enabling the rule, verify enforcement rather than assuming it:
- Open a test pull request against
main. Confirm the merge check status appears in the PR UI and the merge button is disabled with zero approvals. - Add one approval. The button should remain disabled. Add a second — it should enable only if the pipeline has also passed.
- Push a commit that deliberately fails the pipeline (for example, a failing test). Confirm the merge check flips to failed and the button disables again.
- Check the audit log for the block and allow events so you know where to look during a real incident.
Repeat the approval-count test after any rule change. A practical regression check: lower the requirement to one approval in a staging repo, confirm a single approval enables merge, then restore it.
Failure modes
- Pipelines unavailable or misconfigured. If no build status is posted, the check stays unresolved and merges are blocked. This is fail-closed, which is correct for safety but means a Pipelines outage is also a merge outage. Decide in advance who may temporarily relax the rule and how that is logged.
- Flaky pipelines. A gate that fails randomly trains developers to re-run builds until green and to distrust the signal. Track flake rate; a flaky test is a gate defect, not a developer problem.
- Approval bottlenecks. Two required approvals with a small team can stall merges for days. The symptom is developers batching larger PRs to amortize review cost, which makes review worse. Watch PR size and time-to-merge as health metrics.
- Approval reset gaps. Without reset-on-push, a reviewer can approve and the author can then push unreviewed changes. Enable the reset unless you have a deliberate reason not to.
Conditions that would change the design
Revisit the rule when: the team grows enough that two approvals is cheap (raise the bar or add a code-owner requirement); a regulated requirement demands separation of author and approver; you adopt merge queues, which move some enforcement out of merge checks; or pipeline latency grows past the point where blocking on it is acceptable, at which point split fast checks (tests) from slow ones (e2e) and gate only on the fast set.
The design principle underneath all of this: the gate should encode the minimum review and verification your incident history actually justifies, enforced mechanically, audited automatically, and changed deliberately.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.