Auto-merge when CI is green
The “merge approved PRs” pattern in the CI/CD guide only checks whether a pull request (PR) has participants[].approved set — it ignores the actual CI status. This recipe adds the missing piece: poll bb pr checks until every status is SUCCESSFUL, then merge.
What “all-green” means
Section titled “What “all-green” means”bb pr checks <id> --json returns this envelope — each entry in statuses also carries refname, createdOn and uuid, trimmed here:
{ "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 all-green when at least one check exists and every status is SUCCESSFUL. Treat zero checks as “not ready”.
summary counts only three of those states: successful for SUCCESSFUL, failed for FAILED, pending for INPROGRESS. Anything else — STOPPED, or any state Bitbucket adds later — is counted in none of them, so summary.successful + failed + pending can be less than statuses | length. Gate on successful == total, never on failed == 0 and pending == 0.
jq filter for all-green
Section titled “jq filter for all-green”# Returns "true" only if ≥1 check exists and every state is SUCCESSFULbb pr checks 42 --json --jq ' (.statuses | length) > 0 and (.statuses | all(.state == "SUCCESSFUL"))'If you want to also gate on approval:
bb pr view 42 --json --jq ' [.participants[] | select(.approved == true)] | length > 0'Recipe: poll-then-merge
Section titled “Recipe: poll-then-merge”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 minutesPOLL_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') successful=$(echo "$checks_json" | jq '.summary.successful') 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
# successful == total, not "no failures and nothing pending": a STOPPED # check lands in none of the summary buckets, so the looser test would # merge a PR whose only build was cancelled. A STOPPED check keeps this # loop polling until TIMEOUT_SECONDS, which then exits 1. if [ "$total" -gt 0 ] && [ "$successful" -eq "$total" ]; then echo "All $total checks passed for PR #$PR_ID — merging." break fi
echo "PR #$PR_ID: $successful/$total passed, $pending pending — sleeping ${POLL_INTERVAL}s" sleep "$POLL_INTERVAL"done
bb pr merge "$PR_ID" \ -w "$WORKSPACE" -r "$REPO" \ --strategy squash --close-source-branchWORKSPACE=myworkspace REPO=myrepo ./auto-merge-on-green.sh 42Recipe: scan all open PRs
Section titled “Recipe: scan all open PRs”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.
--all is load-bearing. bb pr list defaults to --limit 25, and the Use --limit <n> or --all to see more. hint is printed with table output only — under --json you get 25 rows and no warning, so older approved PRs would never be merged.
#!/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" --all --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 PRsdoneRelated
Section titled “Related”bb pr checks— the underlying commandbb pr merge— merge strategies and flags- Scripting & Automation — JSON output, exit codes, jq patterns