Scripting & Automation - Bitbucket CLI for CI/CD Workflows
JSON output mode
Section titled “JSON output mode”Add --json for machine-readable output:
bb pr list --jsonbb repo list --jsonbb pr view 42 --jsonPaginated list commands return 25 rows by default. Pass --all to fetch every
page, or --limit <n> for a specific cap — --all wins if you pass both.
--limit must be a positive integer; anything else fails with --limit must be a positive integer (error code 5002). There is no --page flag; pagination is
followed internally.
bb pr list --json --allbb pr list --json --limit 100Eleven commands take --limit and --all: repo list, pr list,
pr activity, pr comments list, snippet list, snippet comments list,
pipeline list, commit list, status list, workspace list,
project list. Other list-style commands return their full result set and
accept neither flag — bb pr reviewers list and
bb repo default-reviewers list, for example.
Field selection and --jq
Section titled “Field selection and --jq”Two flags inspired by the gh CLI
slice and filter output without an external jq binary:
# Project to a comma-separated field listbb pr list --json id,title,state
# Filter through the 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.
--jq matches the syntax of the standalone
jq binary, so existing expressions work either
way. These two are equivalent:
bb pr list --json --jq '.pullRequests[].title'bb pr list --json | jq '.pullRequests[].title'jq patterns
Section titled “jq patterns”# Open PR count (--state defaults to OPEN)bb pr list --json --all --jq '.count'
# Merged PR countbb pr list -s MERGED --json --all --jq '.count'
# Project specific fieldsbb pr list --json --all --jq '.pullRequests[] | {id, title, author: .author.display_name}'
# Filter by authorbb pr list --json --all --jq '.pullRequests[] | select((.author.nickname // .author.display_name) == "alice")'
# PR web URLsbb pr list --json --all --jq '.pullRequests[].links.html.href'
# Web diff URL for one PRbb pr diff 42 --web --json --jq '.url'
# PRs updated in the last 7 daysbb pr list --json --all | jq --arg date "$(date -d '7 days ago' -Iseconds 2>/dev/null || \ date -v-7d -Iseconds)" \ '.pullRequests[] | select(.updated_on > $date)'
# PRs targeting mainbb pr list --json --all | jq '.pullRequests[] | select(.destination.branch.name == "main")'
# Group PRs by authorbb pr list --json --all | jq '.pullRequests | group_by(.author.nickname // .author.display_name) | map({author: (.[0].author.nickname // .[0].author.display_name), count: length})'Raw API access (escape hatch)
Section titled “Raw API access (escape hatch)”When no typed command covers what you need, bb api calls any
Bitbucket Cloud 2.0 endpoint through the same authenticated stack. Its output is
already JSON, so --jq works without --json.
# Any endpoint, authenticatedbb api /user --jq '.username'
# Fields become a query string on GET, a JSON body otherwisebb api /repositories/myworkspace/myrepo/pullrequests -X GET -f state=MERGED
# Follow pagination and pull a single field across every pagebb api /repositories/myworkspace/myrepo/pullrequests --paginate \ --jq '.values[].title'
# Create resources that have no typed command yetbb api POST /repositories/{workspace}/{repo}/branch-restrictions --input body.json{workspace} and {repo} placeholders resolve from -w/-r or the current
checkout. See the API command reference for every flag.
Exit codes
Section titled “Exit codes”| 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, read the code field of the JSON error envelope,
which is one of the error codes.
Error handling
Section titled “Error handling”In --json mode, success JSON goes to stdout and the error envelope goes to
stderr. Redirect them separately:
#!/bin/bashset -euo pipefail
if ! bb pr view 999 -w myworkspace -r myrepo --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): $(jq -r '.message' err.json)";; esac exit 1fiThe error envelope carries:
| Field | Notes |
|---|---|
name |
Error class — BBError, AuthError, APIError, … |
code |
Numeric error code |
message |
Human-readable failure description |
context |
Extra detail (ids, keys, args); omitted when the error carries none |
statusCode |
HTTP status — APIError only |
response |
Raw API response body — APIError only, when non-empty |
hint |
Remediation advice; optional, see below |
hint appears only on API errors with status 401, 403, or 404, and is skipped
on 404 when the message already names the missing resource. Treat it as
optional in scripts.
Argument-parsing failures are the exception. An unknown option, a missing
argument, or too many arguments prints a plain text usage error to stderr
and exits 1 even under --json, so jq cannot parse it. The one parse failure
that still emits an envelope is an unknown top-level command — bb bogus --json
returns code 5002, while bb pr bogus --json prints
error: unknown command 'bogus' as plain text.
Environment 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”Automatic retry
Section titled “Automatic retry”API requests that fail with HTTP 429, 502, 503, or 504 are retried up to 3 times with exponential backoff. Add your own retry loop only if you need more than 3 attempts.
OAuth token refresh
Section titled “OAuth token refresh”OAuth access tokens expire after 2 hours. The CLI refreshes them automatically — proactively before expiry and reactively on 401 responses. Long-running OAuth scripts never need to re-authenticate manually.
Scripting best practices
Section titled “Scripting best practices”Always pass -w and -r
Section titled “Always pass -w and -r”Workspace and repository detection reads the git remote of the current checkout. CI runners frequently check out with a non-Bitbucket remote, or no remote at all, so context detection fails there.
# Good - explicit and reliablebb pr list -w myworkspace -r myrepo --json
# Bad - fails when the checkout has no Bitbucket remotebb pr list --jsonAdd delays in loops
Section titled “Add delays in loops”When making many sequential API calls, add short delays to avoid hitting rate limits:
#!/bin/bashset -euo pipefail
WORKSPACE="myworkspace"REPO="myrepo"
for pr_id in $(bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | jq -r '.pullRequests[].id'); do bb pr view "$pr_id" -w "$WORKSPACE" -r "$REPO" --json sleep 1doneExample scripts
Section titled “Example scripts”Batch pull request approval
Section titled “Batch pull request approval”Approve all open pull requests (PRs) from a specific author:
#!/bin/bashset -euo pipefail
WORKSPACE="myworkspace"REPO="myrepo"AUTHOR="trusted-bot"
echo "Finding PRs from $AUTHOR..."
pr_ids=$(bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | \ 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/bashset -euo pipefail
WORKSPACE="myworkspace"REPO="myrepo"
echo "# Open Pull Requests"echo ""echo "Generated: $(date)"echo ""
bb pr list -w "$WORKSPACE" -r "$REPO" --json --all | 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 -euo pipefail
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 --all | \ 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