This guide covers using bb in scripts, CI/CD pipelines, and automation workflows.
- Using bb in Shell Scripts
- JSON Output Mode
- Raw API Access
- Common Automation Patterns
- Exit Codes and Error Handling
- Working with jq
- Non-Interactive Mode
- Example Scripts
- CI/CD Integration
The bb CLI is designed to work well in scripts and automation. Here are the key principles:
#!/bin/bash
set -euo pipefail
# Authenticate using environment variable
export BB_TOKEN="your-access token"
# Run bb commands
bb pr list --json | jq '.[] | .title'For scripted use, set authentication via environment variables:
# Option 1: BB_TOKEN (preferred)
export BB_TOKEN="your-bitbucket-access token"
# Option 2: BITBUCKET_TOKEN (alternative)
export BITBUCKET_TOKEN="your-bitbucket-access token"Create an access token in Bitbucket:
- Go to Personal Settings > access tokens
- Create a new access token with required permissions
- Store it securely (environment variable, secrets manager, etc.)
Always specify the repository explicitly in scripts to avoid dependency on git context:
# Use -R or --repo flag
bb pr list -R myworkspace/myrepo --json
# Or use the global flag
bb --repo myworkspace/myrepo pr list --jsonMost bb commands support --json flag for machine-readable output:
# List PRs as JSON
bb pr list --json
# List issues as JSON
bb issue list --json
# List pipelines as JSON
bb pipeline list --jsonPull Requests:
bb pr list --json[
{
"id": 42,
"title": "Add new feature",
"state": "OPEN",
"source": {
"branch": {
"name": "feature/new-thing"
}
},
"destination": {
"branch": {
"name": "main"
}
},
"author": {
"display_name": "John Doe"
}
}
]Issues:
bb issue list --json[
{
"id": 1,
"title": "Bug in login flow",
"state": "open",
"kind": "bug",
"priority": "critical",
"assignee": "johndoe"
}
]Pipelines:
bb pipeline list --json[
{
"build_number": 123,
"state": "COMPLETED",
"result": "SUCCESSFUL",
"branch": "main",
"commit": "abc1234",
"duration": 180
}
]Use bb api for direct access to any Bitbucket API endpoint:
# GET request (default)
bb api /user
# GET with full endpoint
bb api /repositories/myworkspace/myrepo
# Specify HTTP method
bb api /repositories/myworkspace/myrepo/issues --method POST \
--json title="New issue" \
--json priority="major"# Add custom headers
bb api /user --header "Accept: application/json"
# POST with JSON body
bb api /repositories/workspace/repo/issues \
--method POST \
--json title="Bug report" \
--json content.raw="Description here" \
--json priority="major"
# Read request body from file
bb api /repositories/workspace/repo/src/main/config.json \
--method PUT \
--input config.json
# Read from stdin
echo '{"title": "Test"}' | bb api /repositories/workspace/repo/issues \
--method POST \
--input -# Automatically fetch all pages
bb api /repositories/myworkspace --paginate
# This returns all results combined into a single JSON array# Include response headers
bb api /user --include
# Silent mode (no output, useful for checking status codes)
bb api /user --silent# Get repository details
bb api /repositories/myworkspace/myrepo
# List workspace members
bb api /workspaces/myworkspace/members
# Get pipeline configuration
bb api /repositories/myworkspace/myrepo/pipelines_config
# Create a branch restriction
bb api /repositories/myworkspace/myrepo/branch-restrictions \
--method POST \
--json kind="push" \
--json pattern="main"
# Add a webhook
bb api /repositories/myworkspace/myrepo/hooks \
--method POST \
--json description="CI webhook" \
--json url="https://ci.example.com/webhook" \
--json active=true \
--json events='["repo:push", "pullrequest:created"]'Create a PR automatically after pushing a feature branch:
#!/bin/bash
set -euo pipefail
BRANCH=$(git rev-parse --abbrev-ref HEAD)
BASE_BRANCH="${BASE_BRANCH:-main}"
WORKSPACE="myworkspace"
REPO="myrepo"
# Skip if on main/master
if [[ "$BRANCH" == "main" || "$BRANCH" == "master" ]]; then
echo "Skipping PR creation on main branch"
exit 0
fi
# Check if PR already exists
EXISTING_PR=$(bb pr list -R "$WORKSPACE/$REPO" --json | \
jq -r --arg branch "$BRANCH" '.[] | select(.source.branch.name == $branch) | .id')
if [[ -n "$EXISTING_PR" ]]; then
echo "PR #$EXISTING_PR already exists for branch $BRANCH"
exit 0
fi
# Create PR with auto-filled content from commits
bb pr create -R "$WORKSPACE/$REPO" \
--fill \
--base "$BASE_BRANCH" \
--head "$BRANCH"Close all issues with a specific label:
#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
# Get all open issues of a certain kind
ISSUE_IDS=$(bb issue list -R "$WORKSPACE/$REPO" --state open --kind bug --json | \
jq -r '.[].id')
for id in $ISSUE_IDS; do
echo "Closing issue #$id"
bb issue close -R "$WORKSPACE/$REPO" "$id"
doneBulk update issue priority:
#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
# Get all trivial priority issues
bb issue list -R "$WORKSPACE/$REPO" --priority trivial --json | \
jq -r '.[].id' | while read -r id; do
echo "Updating issue #$id to minor priority"
bb issue edit -R "$WORKSPACE/$REPO" "$id" --priority minor
doneTrigger and monitor pipelines:
#!/bin/bash
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
BRANCH="${1:-main}"
# Trigger pipeline
echo "Triggering pipeline on branch $BRANCH..."
bb pipeline run -R "$WORKSPACE/$REPO" --branch "$BRANCH"
# Wait for pipeline to complete (with timeout)
TIMEOUT=1800 # 30 minutes
ELAPSED=0
INTERVAL=30
while [[ $ELAPSED -lt $TIMEOUT ]]; do
# Get latest pipeline status
STATUS=$(bb pipeline list -R "$WORKSPACE/$REPO" --branch "$BRANCH" --limit 1 --json | \
jq -r '.[0].state')
RESULT=$(bb pipeline list -R "$WORKSPACE/$REPO" --branch "$BRANCH" --limit 1 --json | \
jq -r '.[0].result // empty')
echo "Pipeline status: $STATUS ${RESULT:+($RESULT)}"
if [[ "$STATUS" == "COMPLETED" ]]; then
if [[ "$RESULT" == "SUCCESSFUL" ]]; then
echo "Pipeline completed successfully!"
exit 0
else
echo "Pipeline failed with result: $RESULT"
exit 1
fi
fi
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
done
echo "Pipeline timed out after ${TIMEOUT}s"
exit 1Run custom pipeline with variables:
#!/bin/bash
# Trigger a deployment pipeline
WORKSPACE="myworkspace"
REPO="myrepo"
ENVIRONMENT="${1:-staging}"
bb pipeline run -R "$WORKSPACE/$REPO" \
--branch main \
--custom "deploy-$ENVIRONMENT"| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (command failed) |
#!/bin/bash
set -euo pipefail
# Method 1: Check exit code explicitly
if ! bb pr create --title "Test" --body "Test body"; then
echo "Failed to create PR"
exit 1
fi
# Method 2: Capture output and check
OUTPUT=$(bb pr list --json 2>&1) || {
echo "Failed to list PRs: $OUTPUT"
exit 1
}
# Method 3: Use trap for cleanup
cleanup() {
echo "Script failed, cleaning up..."
}
trap cleanup ERR
bb pr create --title "Test"#!/bin/bash
# Check if any PRs exist
PR_COUNT=$(bb pr list --json | jq 'length')
if [[ "$PR_COUNT" -eq 0 ]]; then
echo "No open PRs found"
exit 0
fi
echo "Found $PR_COUNT open PRs"#!/bin/bash
# Check if API call succeeded and returned expected data
RESPONSE=$(bb api /user 2>&1)
EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
echo "API call failed: $RESPONSE"
exit 1
fi
USERNAME=$(echo "$RESPONSE" | jq -r '.username // empty')
if [[ -z "$USERNAME" ]]; then
echo "Unexpected API response format"
exit 1
fi
echo "Logged in as: $USERNAME"Extract specific fields:
# Get PR titles
bb pr list --json | jq -r '.[].title'
# Get PR IDs and titles
bb pr list --json | jq -r '.[] | "\(.id): \(.title)"'Filter results:
# PRs by specific author
bb pr list --json | jq '[.[] | select(.author.display_name == "John Doe")]'
# Issues with high priority
bb issue list --json | jq '[.[] | select(.priority == "critical" or .priority == "blocker")]'
# Failed pipelines
bb pipeline list --json | jq '[.[] | select(.result == "FAILED")]'Transform output:
# Create CSV from PRs
bb pr list --json | jq -r '.[] | [.id, .title, .state] | @csv'
# Create markdown list
bb pr list --json | jq -r '.[] | "- [\(.title)](\(.url))"'Aggregate data:
# Count PRs by state
bb pr list --json | jq 'group_by(.state) | map({state: .[0].state, count: length})'
# Sum pipeline durations
bb pipeline list --json | jq '[.[].duration] | add'Complex transformations:
# Get PR summary with reviewers
bb pr list --json | jq '
.[] | {
id,
title,
branch: .source.branch.name,
author: .author.display_name,
created: .created_on
}
'When running in non-interactive environments (CI/CD, cron jobs), ensure all inputs are provided via flags:
# Fully non-interactive PR creation
bb pr create \
--title "Automated PR: Update dependencies" \
--body "This PR was created automatically by CI." \
--base main \
--head feature/deps-update \
--repo myworkspace/myrepo# Fully non-interactive issue creation
bb issue create \
--title "Automated: Performance regression detected" \
--body "Performance dropped by 15% in latest build." \
--kind bug \
--priority critical \
--repo myworkspace/myrepoThe bb CLI automatically detects when stdin is not a TTY and will error if required input is missing:
# This will fail in non-interactive mode without --title
echo "" | bb pr create
# Error: --title flag is required when not running interactivelyDisable color output in scripts:
export NO_COLOR=1
# or
export BB_NO_COLOR=1#!/bin/bash
# Generate a daily summary of open PRs
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
OUTPUT_FILE="pr-summary-$(date +%Y%m%d).md"
cat > "$OUTPUT_FILE" << EOF
# Pull Request Summary - $(date +%Y-%m-%d)
## Open PRs
EOF
bb pr list -R "$WORKSPACE/$REPO" --state OPEN --json | jq -r '
.[] | "### PR #\(.id): \(.title)\n- **Author:** \(.author.display_name)\n- **Branch:** \(.source.branch.name) -> \(.destination.branch.name)\n- **Created:** \(.created_on)\n"
' >> "$OUTPUT_FILE"
echo "Summary written to $OUTPUT_FILE"#!/bin/bash
# Find PRs older than 7 days without recent activity
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
DAYS_OLD=7
CUTOFF_DATE=$(date -d "$DAYS_OLD days ago" +%Y-%m-%dT%H:%M:%S 2>/dev/null || \
date -v-${DAYS_OLD}d +%Y-%m-%dT%H:%M:%S)
echo "Finding PRs older than $DAYS_OLD days..."
bb pr list -R "$WORKSPACE/$REPO" --state OPEN --json | jq -r --arg cutoff "$CUTOFF_DATE" '
.[] | select(.updated_on < $cutoff) |
"PR #\(.id): \(.title) (last updated: \(.updated_on))"
'#!/bin/bash
# Create a release PR merging develop into main
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
VERSION="${1:?Usage: $0 <version>}"
RELEASE_BRANCH="release/$VERSION"
echo "Creating release $VERSION..."
# Create release branch
git checkout develop
git pull origin develop
git checkout -b "$RELEASE_BRANCH"
git push -u origin "$RELEASE_BRANCH"
# Create PR
bb pr create -R "$WORKSPACE/$REPO" \
--title "Release $VERSION" \
--body "## Release $VERSION
### Changes
$(git log --oneline main..HEAD | sed 's/^/- /')
### Checklist
- [ ] Version bumped
- [ ] Changelog updated
- [ ] Tests passing" \
--base main \
--head "$RELEASE_BRANCH"
echo "Release PR created!"#!/bin/bash
# Sync a forked repository with upstream
set -euo pipefail
UPSTREAM_WORKSPACE="upstream-workspace"
UPSTREAM_REPO="upstream-repo"
FORK_WORKSPACE="my-workspace"
FORK_REPO="my-fork"
# Get latest from upstream using API
LATEST_SHA=$(bb api /repositories/$UPSTREAM_WORKSPACE/$UPSTREAM_REPO/refs/branches/main | \
jq -r '.target.hash')
echo "Latest upstream commit: $LATEST_SHA"
# Sync fork (via git)
git fetch upstream
git checkout main
git merge upstream/main
git push origin main
echo "Fork synced!"#!/bin/bash
# Monitor pipeline and send notification on completion
set -euo pipefail
WORKSPACE="myworkspace"
REPO="myrepo"
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"
BUILD_NUM="${1:?Usage: $0 <build-number>}"
wait_for_pipeline() {
local build=$1
local timeout=3600
local elapsed=0
while [[ $elapsed -lt $timeout ]]; do
local status=$(bb pipeline list -R "$WORKSPACE/$REPO" --json | \
jq -r --argjson num "$build" '.[] | select(.build_number == $num) | .state')
local result=$(bb pipeline list -R "$WORKSPACE/$REPO" --json | \
jq -r --argjson num "$build" '.[] | select(.build_number == $num) | .result // empty')
if [[ "$status" == "COMPLETED" ]]; then
echo "$result"
return 0
fi
sleep 30
elapsed=$((elapsed + 30))
done
echo "TIMEOUT"
return 1
}
RESULT=$(wait_for_pipeline "$BUILD_NUM")
# Send Slack notification if webhook configured
if [[ -n "$SLACK_WEBHOOK" ]]; then
if [[ "$RESULT" == "SUCCESSFUL" ]]; then
COLOR="good"
TEXT="Pipeline #$BUILD_NUM completed successfully!"
else
COLOR="danger"
TEXT="Pipeline #$BUILD_NUM failed: $RESULT"
fi
curl -s -X POST "$SLACK_WEBHOOK" \
-H 'Content-type: application/json' \
-d "{\"attachments\":[{\"color\":\"$COLOR\",\"text\":\"$TEXT\"}]}"
fi
[[ "$RESULT" == "SUCCESSFUL" ]]name: Bitbucket Sync
on:
push:
branches: [main]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install bb CLI
run: |
curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
sudo mv bb /usr/local/bin/
- name: Create PR in Bitbucket
env:
BB_TOKEN: ${{ secrets.BITBUCKET_access token }}
run: |
bb pr create \
--repo myworkspace/myrepo \
--title "Sync from GitHub: ${{ github.sha }}" \
--body "Automated sync from GitHub repository" \
--base main \
--head sync-${{ github.sha }}stages:
- deploy
variables:
BB_TOKEN: $BITBUCKET_access token
deploy:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl jq
- curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
- mv bb /usr/local/bin/
script:
- |
bb pipeline run \
--repo myworkspace/myrepo \
--branch main \
--custom deploy-production
only:
- mainpipeline {
agent any
environment {
BB_TOKEN = credentials('bitbucket-access token')
}
stages {
stage('Setup') {
steps {
sh '''
curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
chmod +x bb
'''
}
}
stage('Create PR') {
steps {
sh '''
./bb pr create \
--repo myworkspace/myrepo \
--title "Jenkins Build #${BUILD_NUMBER}" \
--body "Automated PR from Jenkins" \
--fill
'''
}
}
stage('Trigger Pipeline') {
steps {
sh '''
./bb pipeline run \
--repo myworkspace/myrepo \
--branch ${BRANCH_NAME}
'''
}
}
}
}version: 2.1
jobs:
bitbucket-sync:
docker:
- image: cimg/base:stable
steps:
- checkout
- run:
name: Install bb CLI
command: |
curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
sudo mv bb /usr/local/bin/
- run:
name: Sync to Bitbucket
command: |
bb pr create \
--repo myworkspace/myrepo \
--title "CircleCI Sync: ${CIRCLE_SHA1:0:7}" \
--body "Automated sync from CircleCI build #${CIRCLE_BUILD_NUM}"
workflows:
sync:
jobs:
- bitbucket-sync:
context: bitbucket-credentialstrigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: bitbucket-credentials
steps:
- script: |
curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
sudo mv bb /usr/local/bin/
displayName: 'Install bb CLI'
- script: |
bb pipeline run \
--repo $(BITBUCKET_WORKSPACE)/$(BITBUCKET_REPO) \
--branch main
displayName: 'Trigger Bitbucket Pipeline'
env:
BB_TOKEN: $(BITBUCKET_access token)# bitbucket-pipelines.yml
pipelines:
custom:
create-release-pr:
- step:
name: Create Release PR
script:
- curl -sL https://github.com/rbansal42/bitbucket-cli/releases/latest/download/bb_linux_amd64.tar.gz | tar xz
- chmod +x bb
- |
./bb pr create \
--repo $BITBUCKET_WORKSPACE/$BITBUCKET_REPO_SLUG \
--title "Release $(date +%Y.%m.%d)" \
--body "Automated release PR" \
--base main \
--head develop
services:
- docker- Always use
--repoin scripts - Don't rely on git context detection - Use
--jsonfor parsing - Human-readable output may change between versions - Handle errors gracefully - Check exit codes and validate JSON responses
- Use environment variables for secrets - Never hardcode tokens
- Set timeouts - Especially for pipeline monitoring scripts
- Use
set -euo pipefail- Fail fast on errors in bash scripts - Log actions - Include echo statements for debugging
- Test locally first - Verify scripts before running in CI/CD