Auto-merge when CI is green
This recipe requires an approval and an explicit list of CI check keys. It reads every status page with bb api --paginate, then checks that the PR head still matches the commit inspected.
What “all-green” means
Section titled “What “all-green” means”Each required key must have exactly one status with state: SUCCESSFUL. Every returned status must also be successful. Missing checks, zero checks, duplicate keys, INPROGRESS, STOPPED and unknown states never permit a merge. A failed check aborts the run.
Set REQUIRED_CHECK_KEYS to the exact keys your CI publishes, separated by spaces. These are status keys, not display names. Keep keys free of whitespace for this script.
bb pr checks 42 --watch waits for running checks and exits non-zero when one failed or stopped, but it does not know which keys you require, so this recipe reads the statuses itself.
jq filter for all-green
Section titled “jq filter for all-green”bb api "/repositories/$WORKSPACE/$REPO/pullrequests/42/statuses" --paginate --json \ | jq --arg keys 'build test' ' .values as $statuses | ($keys | split(" ")) as $required | ($statuses | length) > 0 and all($statuses[]; .state == "SUCCESSFUL") and ([$statuses[].key] | length == (unique | length)) and all($required[]; . as $key | [$statuses[] | select(.key == $key)] | length == 1) 'Recipe: poll-then-merge
Section titled “Recipe: poll-then-merge”Save as auto-merge-on-green.sh. It exits 0 after a successful merge, 1 on errors or timeout, and 2 when --once finds a PR that is not ready.
#!/bin/bashset -euo pipefail
WORKSPACE="${WORKSPACE:?set WORKSPACE}"REPO="${REPO:?set REPO}"PR_ID="${1:?Usage: $0 <pr-id> [--once]}"REQUIRED_CHECK_KEYS="${REQUIRED_CHECK_KEYS:?set required CI check keys}"TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-1800}"POLL_INTERVAL="${POLL_INTERVAL:-30}"once="${2:-}"path="/repositories/$WORKSPACE/$REPO/pullrequests/$PR_ID"required=$(printf '%s' "$REQUIRED_CHECK_KEYS" | jq -Rc 'split(" ") | map(select(length > 0)) | unique')jq -e 'length > 0' <<< "$required" >/dev/null
deadline=$(( $(date +%s) + TIMEOUT_SECONDS ))while true; do pr=$(bb api "$path" --json) [ "$(jq -r '.state' <<< "$pr")" = OPEN ] || exit 2 head=$(jq -er '.source.commit.hash | select(type == "string" and length > 0)' <<< "$pr") statuses=$(bb api "$path/statuses" --paginate --json) jq -e '.values | type == "array"' <<< "$statuses" >/dev/null
if jq -e 'any(.values[]; .state == "FAILED")' <<< "$statuses" >/dev/null; then echo "PR #$PR_ID has a failed check." >&2 exit 1 fi
if jq -e --argjson required "$required" ' .values as $statuses | ($statuses | length) > 0 and all($statuses[]; .state == "SUCCESSFUL") and ([$statuses[].key] | length == (unique | length)) and all($required[]; . as $key | [$statuses[] | select(.key == $key)] | length == 1) ' <<< "$statuses" >/dev/null; then current=$(bb api "$path" --json) if jq -e --arg head "$head" ' .state == "OPEN" and .source.commit.hash == $head and any(.participants[]?; .approved == true) ' <<< "$current" >/dev/null; then bb pr merge "$PR_ID" -w "$WORKSPACE" -r "$REPO" \ --strategy squash --close-source-branch exit 0 fi fi
[ "$once" != --once ] || exit 2 if [ "$(date +%s)" -ge "$deadline" ]; then echo "Timed out waiting for PR #$PR_ID checks and approval." >&2 exit 1 fi sleep "$POLL_INTERVAL"doneWORKSPACE=myworkspace REPO=myrepo REQUIRED_CHECK_KEYS="build test" \ ./auto-merge-on-green.sh 42Recipe: scan all open PRs
Section titled “Recipe: scan all open PRs”Save the script above alongside this one. Run it on a schedule to attempt each ready PR once. A failed check, API error or rejected merge makes the scan exit nonzero after processing the remaining PRs.
#!/bin/bashset -euo pipefail
WORKSPACE="${WORKSPACE:?set WORKSPACE}"REPO="${REPO:?set REPO}"export WORKSPACE REPOexport REQUIRED_CHECK_KEYS="${REQUIRED_CHECK_KEYS:?set required CI check keys}"script_dir=$(cd "$(dirname "$0")" && pwd)open_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --all --json --jq '.pullRequests[].id')failed=0
for pr_id in $open_prs; do if "$script_dir/auto-merge-on-green.sh" "$pr_id" --once; then echo "Merged PR #$pr_id" else status=$? if [ "$status" -eq 2 ]; then echo "PR #$pr_id: not ready" else echo "PR #$pr_id: check or merge failed" >&2 failed=1 fi fi sleep 2doneexit "$failed"Both scripts need Bash and external jq. --all makes the PR scan include older open PRs rather than only the default first 25.