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.
Parsing with jq
Section titled “Parsing with jq”jq is the standard tool for processing JSON:
# List all PR titlesbb pr list --json | jq '.pullRequests[].title'
# Get PR countbb pr list --json | jq '.count'
# Filter by statebb pr list -s MERGED --json | jq '.count'
# Get 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")'
# Get PR URLsbb pr list --json | jq -r '.pullRequests[].links.html.href'
# Get web diff URL for a PRbb pr diff 42 --web --json | jq -r '.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)" \ '.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 standard exit codes:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Error (authentication, API, validation, etc.) |
Checking 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.
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 API Failures
Section titled “Handle API Failures”Implement retry logic for transient failures:
#!/bin/bash
max_retries=3retry_delay=5
for ((i=1; i<=max_retries; i++)); do if bb pr list -w workspace -r repo --json > prs.json 2>&1; then break fi
if [ $i -lt $max_retries ]; then echo "Attempt $i failed, retrying in ${retry_delay}s..." sleep $retry_delay else echo "All attempts failed" exit 1 fidoneRespect Rate Limits
Section titled “Respect Rate Limits”Add delays between requests:
#!/bin/bash
for pr_id in $(bb pr list --json | jq -r '.pullRequests[].id'); do bb pr view "$pr_id" --json sleep 1 # Rate limit protectiondoneExample 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 1doneRepository Backup Script
Section titled “Repository Backup Script”Clone all repositories in a workspace:
#!/bin/bashset -e
WORKSPACE="myworkspace"BACKUP_DIR="./bitbucket-backup-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"cd "$BACKUP_DIR"
echo "Fetching repository list..."repos=$(bb repo list -w "$WORKSPACE" --json | jq -r '.repositories[].name')
for repo in $repos; do echo "Cloning $repo..." bb repo clone "$WORKSPACE/$repo" || echo "Failed to clone $repo" sleep 1done
echo "Backup complete: $BACKUP_DIR"PR Diff Analysis
Section titled “PR Diff Analysis”Analyze changes across all open PRs:
#!/bin/bash
WORKSPACE="myworkspace"REPO="myrepo"
echo "Analyzing open PRs..."echo ""
total_files=0total_additions=0total_deletions=0
for pr_id in $(bb pr list -w "$WORKSPACE" -r "$REPO" --json | jq -r '.pullRequests[].id'); do stats=$(bb pr diff "$pr_id" -w "$WORKSPACE" -r "$REPO" --stat --json 2>/dev/null)
if [ -n "$stats" ]; then files=$(echo "$stats" | jq '.filesChanged // 0') additions=$(echo "$stats" | jq '.totalAdditions // 0') deletions=$(echo "$stats" | jq '.totalDeletions // 0')
echo "PR #$pr_id: $files files, +$additions -$deletions"
total_files=$((total_files + files)) total_additions=$((total_additions + additions)) total_deletions=$((total_deletions + deletions)) fi
sleep 1done
echo ""echo "Total: $total_files files, +$total_additions -$total_deletions"Python Integration
Section titled “Python Integration”import subprocessimport json
def run_bb(args): """Run bb command and return parsed JSON output.""" result = subprocess.run( ['bb'] + args + ['--json'], capture_output=True, text=True ) if result.returncode != 0: error = json.loads(result.stderr) raise Exception(f"bb command failed [{error['code']}]: {error['message']}") return json.loads(result.stdout)
# List PRsprs = run_bb(['pr', 'list', '-w', 'workspace', '-r', 'repo'])for pr in prs['pullRequests']: print(f"#{pr['id']}: {pr['title']}")
# Get PR detailspr = run_bb(['pr', 'view', '42', '-w', 'workspace', '-r', 'repo'])print(f"State: {pr['state']}")Bun/Node.js Integration
Section titled “Bun/Node.js Integration”You can call the bb CLI from Bun or Node.js scripts. Note that the CLI itself requires Bun runtime, but you can invoke it from any JavaScript runtime.
import { execSync } from 'child_process';
function runBB(args) { const result = execSync(`bb ${args.join(' ')} --json`, { encoding: 'utf8' }); return JSON.parse(result);}
// List PRsconst prs = runBB(['pr', 'list', '-w', 'workspace', '-r', 'repo']);prs.pullRequests.forEach(pr => { console.log(`#${pr.id}: ${pr.title}`);});