Skip to content

Auto-merge when CI is green

The “merge approved PRs” pattern in the CI/CD guide only checks participants[].approved — it ignores the actual CI status. This recipe adds the missing piece: poll bb pr checks until every status is SUCCESSFUL, then merge.

bb pr checks <id> --json returns a stable envelope. The relevant shape:

{
"pullRequestId": 42,
"workspace": "myworkspace",
"repoSlug": "myrepo",
"summary": {
"successful": 3,
"failed": 0,
"pending": 0
},
"statuses": [
{
"key": "build",
"name": "Build & Test",
"state": "SUCCESSFUL",
"description": "Build #1234 succeeded",
"url": "https://ci.example.com/build/1234",
"updatedOn": "2025-01-01T12:00:00Z"
}
]
}

Possible state values: SUCCESSFUL, FAILED, INPROGRESS, STOPPED.

A PR is considered all-green when at least one check exists and every status is SUCCESSFUL. Treat zero checks as “not ready” — most teams want at least one passing build before auto-merge fires.

Terminal window
# Returns "true" only if ≥1 check exists and every state is SUCCESSFUL
bb pr checks 42 --json --jq '
(.statuses | length) > 0
and (.statuses | all(.state == "SUCCESSFUL"))
'

If you want to also gate on approval:

Terminal window
bb pr view 42 --json --jq '
[.participants[] | select(.approved == true)] | length > 0
'

Drop this into a scheduled CI job or run locally. It polls a single PR until checks settle, then merges.

#!/bin/bash
# auto-merge-on-green.sh - Wait for CI to pass, then merge.
set -euo pipefail
WORKSPACE="${WORKSPACE:-myworkspace}"
REPO="${REPO:-myrepo}"
PR_ID="${1:?Usage: $0 <pr-id>}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-1800}" # 30 minutes
POLL_INTERVAL="${POLL_INTERVAL:-30}"
deadline=$(( $(date +%s) + TIMEOUT_SECONDS ))
while true; do
if [ "$(date +%s)" -gt "$deadline" ]; then
echo "Timed out waiting for PR #$PR_ID checks" >&2
exit 1
fi
checks_json=$(bb pr checks "$PR_ID" -w "$WORKSPACE" -r "$REPO" --json)
failed=$(echo "$checks_json" | jq '.summary.failed')
pending=$(echo "$checks_json" | jq '.summary.pending')
total=$(echo "$checks_json" | jq '.statuses | length')
if [ "$failed" -gt 0 ]; then
echo "PR #$PR_ID has $failed failed check(s); aborting." >&2
exit 1
fi
if [ "$total" -gt 0 ] && [ "$pending" -eq 0 ]; then
echo "All $total checks passed for PR #$PR_ID — merging."
break
fi
echo "PR #$PR_ID: $pending pending / $total total — sleeping ${POLL_INTERVAL}s"
sleep "$POLL_INTERVAL"
done
bb pr merge "$PR_ID" \
-w "$WORKSPACE" -r "$REPO" \
--strategy squash --close-source-branch
Terminal window
WORKSPACE=myworkspace REPO=myrepo ./auto-merge-on-green.sh 42

Run this on a schedule (cron, GitHub Actions cron trigger, Bitbucket Pipelines schedule). It merges every approved PR whose checks are all green and skips the rest silently.

#!/bin/bash
# auto-merge-scan.sh - Merge every approved, all-green PR.
set -euo pipefail
WORKSPACE="${WORKSPACE:-myworkspace}"
REPO="${REPO:-myrepo}"
open_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json --jq '.pullRequests[].id')
for pr_id in $open_prs; do
approved=$(bb pr view "$pr_id" -w "$WORKSPACE" -r "$REPO" --json --jq '
[.participants[] | select(.approved == true)] | length > 0
')
[ "$approved" = "true" ] || { echo "PR #$pr_id: not approved"; continue; }
green=$(bb pr checks "$pr_id" -w "$WORKSPACE" -r "$REPO" --json --jq '
(.statuses | length) > 0
and (.statuses | all(.state == "SUCCESSFUL"))
')
[ "$green" = "true" ] || { echo "PR #$pr_id: checks not green"; continue; }
echo "Merging PR #$pr_id"
if ! bb pr merge "$pr_id" -w "$WORKSPACE" -r "$REPO" \
--strategy squash --close-source-branch; then
echo "PR #$pr_id: merge failed (conflicts or branch policy)" >&2
fi
sleep 2 # gentle pacing across many PRs
done