Scripting & Automation - Bitbucket CLI for CI/CD Workflows
The Bitbucket CLI is designed for both interactive use and automation. This guide covers JSON output, exit codes, and scripting patterns.
JSON Output Mode
Section titled “JSON Output Mode”All commands support the --json flag for machine-readable output:
bb pr list --jsonbb repo list --jsonbb pr view 42 --jsonFor list-style commands, results are limited by default. Pass --limit when
your automation needs more rows.
Built-in Field Selection and jq
Section titled “Built-in Field Selection and jq”Two flags inspired by the gh CLI
let you slice and filter output without an external jq binary:
# Project to a comma-separated field listbb pr list --json id,title,state
# Pipe through embedded jq (requires --json)bb pr list --json --jq '.pullRequests[].title'
# Combine bothbb pr list --json id,title,state \ --jq '.[] | select(.state == "OPEN") | .title'When --json fields is passed against a list-style command, the wrapper
envelope is dropped and the field list is projected per-item — the result is
a flat array. See JSON Output reference for details.
Parsing with jq
Section titled “Parsing with jq”The built-in --jq matches the syntax of the standalone
jq binary, so existing expressions work
either way:
# Built-in (no external dependency, cross-platform)bb pr list --json --jq '.pullRequests[].title'
# External jq (equivalent)bb pr list --json | jq '.pullRequests[].title'
# Get PR countbb pr list --json --jq '.count'
# Filter by statebb pr list -s MERGED --json --jq '.count'
# Project specific fieldsbb pr list --json --jq '.pullRequests[] | {id, title, author: .author.display_name}'
# Filter by authorbb pr list --json --jq '.pullRequests[] | select((.author.nickname // .author.display_name) == "alice")'
# PR URLsbb pr list --json --jq '.pullRequests[].links.html.href'
# Web diff URL for a PRbb pr diff 42 --web --json --jq '.url'Common jq Patterns
Section titled “Common jq Patterns”# PRs updated in the last 7 daysbb pr list --json | jq --arg date "$(date -d '7 days ago' -Iseconds 2>/dev/null || \ date -v-7d -Iseconds)" \ '.pullRequests[] | select(.updated_on > $date)'
# PRs targeting main branchbb pr list --json | jq '.pullRequests[] | select(.destination.branch.name == "main")'
# Group PRs by authorbb pr list --json | jq '.pullRequests | group_by(.author.nickname // .author.display_name) | map({author: (.[0].author.nickname // .[0].author.display_name), count: length})'Exit Codes
Section titled “Exit Codes”The CLI uses a deliberately small exit-code surface:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Any failure (authentication, API, validation, network, jq, …) |
There is no 2/3/etc. mapping — every failure exits with 1. To branch
on the specific failure mode in a script, parse the JSON error written to
stderr and read its code field, which corresponds to one of the
error codes.
if ! bb pr view 999 --json > out.json 2> err.json; then code=$(jq -r '.code' err.json) case "$code" in 1001|1002|1003) echo "auth problem";; 2002) echo "PR not found";; *) echo "other failure ($code)";; esacfiChecking Exit Codes
Section titled “Checking Exit Codes”#!/bin/bash
if bb pr list -w myworkspace -r myrepo > /dev/null 2>&1; then echo "Success"else echo "Failed with exit code: $?"fiError Handling Patterns
Section titled “Error Handling Patterns”#!/bin/bashset -e # Exit on any error
# Or handle errors explicitlybb pr list --json > prs.json || { echo "Failed to list PRs" exit 1}if ! bb config get invalidKey --json > out.json 2> err.json; then jq '.' err.json exit 1fiEnvironment Variables
Section titled “Environment Variables”For non-interactive scripts, use environment variables:
export BB_USERNAME=myuserexport BB_API_TOKEN=ATBB_token
bb auth login # Uses env varsbb pr list -w workspace -r repoSee Environment Variables Reference for details.
Built-in Resilience
Section titled “Built-in Resilience”The CLI handles common transient failures automatically — you don’t need to implement this yourself in most cases.
Automatic Retry
Section titled “Automatic Retry”API requests that fail with transient errors (HTTP 429, 502, 503, 504) are retried automatically up to 3 times with exponential backoff. This means rate-limited or temporarily unavailable responses are handled without any extra scripting.
OAuth Token Refresh
Section titled “OAuth Token Refresh”OAuth access tokens expire after 2 hours. The CLI refreshes them automatically — both proactively (before expiry) and reactively (on 401 responses). Long-running scripts using OAuth don’t need manual re-authentication.
Scripting Best Practices
Section titled “Scripting Best Practices”Always Use Explicit Flags
Section titled “Always Use Explicit Flags”Don’t rely on context detection in scripts:
# Good - explicit and reliablebb pr list -w myworkspace -r myrepo --json
# Bad - may fail if context can't be detectedbb pr list --jsonHandle Persistent Failures
Section titled “Handle Persistent Failures”The CLI retries transient errors automatically (see Built-in Resilience above). For persistent failures, handle the exit code:
#!/bin/bash
if ! bb pr list -w workspace -r repo --json > prs.json 2>error.json; then echo "Failed to list PRs" cat error.json exit 1fiAdd Delays in Loops
Section titled “Add Delays in Loops”When making many sequential API calls, add short delays to avoid hitting rate limits:
#!/bin/bash
for pr_id in $(bb pr list --json | jq -r '.pullRequests[].id'); do bb pr view "$pr_id" --json sleep 1doneExample Scripts
Section titled “Example Scripts”Batch PR Approval
Section titled “Batch PR Approval”Approve all open PRs from a specific author:
#!/bin/bashset -e
WORKSPACE="myworkspace"REPO="myrepo"AUTHOR="trusted-bot"
echo "Finding PRs from $AUTHOR..."
pr_ids=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json | \ jq -r --arg author "$AUTHOR" '.pullRequests[] | select((.author.nickname // .author.display_name) == $author) | .id')
for pr_id in $pr_ids; do echo "Approving PR #$pr_id..." bb pr approve "$pr_id" -w "$WORKSPACE" -r "$REPO" sleep 1done
echo "Done!"PR Status Report
Section titled “PR Status Report”Generate a markdown report of open PRs:
#!/bin/bash
WORKSPACE="myworkspace"REPO="myrepo"
echo "# Open Pull Requests"echo ""echo "Generated: $(date)"echo ""
bb pr list -w "$WORKSPACE" -r "$REPO" --json | jq -r '.pullRequests[] | "## PR #\(.id): \(.title)\n" + "- **Author:** \(.author.display_name)\n" + "- **Branch:** \(.source.branch.name) → \(.destination.branch.name)\n" + "- **Created:** \(.created_on)\n" + "- **Link:** \(.links.html.href)\n"'Auto-Close Stale PRs
Section titled “Auto-Close Stale PRs”Decline PRs not updated in 30 days:
#!/bin/bashset -e
WORKSPACE="myworkspace"REPO="myrepo"DAYS_OLD=30
cutoff=$(date -d "$DAYS_OLD days ago" -Iseconds 2>/dev/null || \ date -v-${DAYS_OLD}d -Iseconds) # macOS fallback
echo "Finding PRs older than $DAYS_OLD days..."
stale_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json | \ jq -r --arg cutoff "$cutoff" '.pullRequests[] | select(.updated_on < $cutoff) | .id')
if [ -z "$stale_prs" ]; then echo "No stale PRs found" exit 0fi
for pr_id in $stale_prs; do echo "Declining stale PR #$pr_id..." bb pr decline "$pr_id" -w "$WORKSPACE" -r "$REPO" sleep 1done