Skip to content

CI/CD Integration - GitHub Actions, GitLab, Jenkins, CircleCI

The Bitbucket CLI automates pull request (PR) workflows, reports build statuses back to commits, and triggers Bitbucket Pipelines from any CI system.

  1. Store credentials as secrets in your CI/CD platform.

    • BB_USERNAME — your Bitbucket username
    • BB_API_TOKEN — your Bitbucket API token
  2. Install Bun and the CLI. The CLI is Bun-only; it exits immediately under Node.js.

    Terminal window
    curl -fsSL https://bun.sh/install | bash
    export PATH="$HOME/.bun/bin:$PATH"
    bun install -g @pilatos/bitbucket-cli

    On an image that already ships Bun (oven/bun:latest) skip the curl line and keep the other two — those images contain no Node.js and no npm.

  3. Authenticate. With BB_API_TOKEN set in the environment, bb auth login takes the API-token path and stores the credentials. With no token set it opens a browser for OAuth and waits five minutes for a callback that never arrives in a headless runner.

    Terminal window
    bb auth login
  4. Run commands with explicit workspace and repository flags.

    Terminal window
    bb pr list -w myworkspace -r myrepo --json

List commands return 25 items by default. Pass --limit <n> to raise or lower that cap, or --all to fetch every page — --all overrides --limit.

Terminal window
bb pr list -w myworkspace -r myrepo --all --json

See Global Flags.

--json [fields] and --jq <expression> are global flags. --jq runs an embedded jq, so it works on minimal CI images and on Windows agents that have no jq binary. --jq requires --json.

Terminal window
# Project to a comma-separated field list
bb pr list -w myworkspace -r myrepo --json id,title
# Filter with the embedded jq
bb pr list -w myworkspace -r myrepo --all --json --jq '.pullRequests[].id'

Projection runs before jq, and --json <fields> drops the envelope on list commands — count disappears once you project. See Scripting & Automation.


Each snippet shows the platform-specific part: how secrets reach the job, and how ~/.bun/bin gets on PATH. The install and bb auth login steps are the same everywhere.

name: Bitbucket PR Check
on:
push:
branches: [main]
pull_request:
jobs:
check-bitbucket-prs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install Bitbucket CLI
run: bun install -g @pilatos/bitbucket-cli
- name: List open PRs
env:
BB_USERNAME: ${{ secrets.BB_USERNAME }}
BB_API_TOKEN: ${{ secrets.BB_API_TOKEN }}
run: |
bb auth login
bb pr list -w myworkspace -r myrepo --json --jq '.count'

oven-sh/setup-bun puts bun on PATH for you, including globally installed binaries.

stages:
- check
variables:
BB_WORKSPACE: myworkspace
BB_REPO: myrepo
check-prs:
stage: check
image: oven/bun:latest
before_script:
- bun install -g @pilatos/bitbucket-cli
- export PATH="$HOME/.bun/bin:$PATH"
- bb auth login
script:
- bb pr list -w $BB_WORKSPACE -r $BB_REPO --json
- bb repo view -w $BB_WORKSPACE -r $BB_REPO

Define BB_USERNAME and BB_API_TOKEN under Settings > CI/CD > Variables (masked, protected). GitLab exports project variables into the job environment, so no variables: entry is needed for them.

image: oven/bun:latest
pipelines:
default:
- step:
name: Check PRs
script:
- bun install -g @pilatos/bitbucket-cli
- export PATH="$HOME/.bun/bin:$PATH"
- bb auth login
- bb pr list -w $BITBUCKET_WORKSPACE -r $BITBUCKET_REPO_SLUG --json
pull-requests:
'**':
- step:
name: PR Info
script:
- bun install -g @pilatos/bitbucket-cli
- export PATH="$HOME/.bun/bin:$PATH"
- bb auth login
- bb pr view $BITBUCKET_PR_ID -w $BITBUCKET_WORKSPACE -r $BITBUCKET_REPO_SLUG
definitions:
caches:
bun: ~/.bun

Pipelines injects BITBUCKET_WORKSPACE, BITBUCKET_REPO_SLUG, and BITBUCKET_PR_ID, which the snippet above passes to -w, -r, and bb pr view.

pipeline {
agent any
environment {
BB_USERNAME = credentials('bitbucket-username')
BB_API_TOKEN = credentials('bitbucket-token')
}
stages {
stage('Setup') {
steps {
sh 'curl -fsSL https://bun.sh/install | bash'
sh 'export PATH="$HOME/.bun/bin:$PATH" && bun install -g @pilatos/bitbucket-cli'
sh 'export PATH="$HOME/.bun/bin:$PATH" && bb auth login'
}
}
stage('Check PRs') {
steps {
sh '''
export PATH="$HOME/.bun/bin:$PATH"
bb pr list -w myworkspace -r myrepo --json --jq '.count'
'''
}
}
}
}

Each sh step is a fresh shell, so PATH has to be exported in every one.

To merge approved PRs from Jenkins, run the script in Merge approved PRs automatically as a build step.

trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: bitbucket-credentials # Variable group with BB_USERNAME, BB_API_TOKEN
steps:
- script: |
curl -fsSL https://bun.sh/install | bash
echo "##vso[task.prependpath]$HOME/.bun/bin"
displayName: 'Install Bun'
- script: bun install -g @pilatos/bitbucket-cli
displayName: 'Install Bitbucket CLI'
- script: |
bb auth login
bb pr list -w myworkspace -r myrepo --json
displayName: 'List PRs'
env:
BB_USERNAME: $(BB_USERNAME)
BB_API_TOKEN: $(BB_API_TOKEN)

##vso[task.prependpath] puts ~/.bun/bin on PATH for every later step.


bb status set <sha> creates or updates a build status on a commit. It is idempotent per --key, so calling it again with the same key replaces the previous status instead of adding a second one.

Terminal window
bb status set "$GITHUB_SHA" -w myworkspace -r myrepo \
--key CI --state INPROGRESS --url "$RUN_URL"
# ... run the build ...
bb status set "$GITHUB_SHA" -w myworkspace -r myrepo \
--key CI --state SUCCESSFUL --url "$RUN_URL" --description "All tests passed"

--state accepts INPROGRESS, SUCCESSFUL, FAILED, or STOPPED. --name, --description, and --refname are optional. Read statuses back with bb status list <sha>.

Trigger a Bitbucket pipeline from another CI system

Section titled “Trigger a Bitbucket pipeline from another CI system”
Terminal window
# Latest commit on a branch
bb pipeline run -w myworkspace -r myrepo --branch main
# A custom pipeline from bitbucket-pipelines.yml, with variables
bb pipeline run -w myworkspace -r myrepo \
--pipeline deploy-prod --var ENV=prod --var DRY_RUN=false

bb pipeline list --status FAILED, bb pipeline view <id>, bb pipeline logs <id> --step <n>, and bb pipeline stop <id> cover the rest. See bb pipeline.

Create the PR only if one does not already exist for the source branch. This works the same whether the branch came from a feature push or a scheduled dependency bump.

Terminal window
BRANCH=${GITHUB_REF#refs/heads/}
EXISTING=$(bb pr list -w myworkspace -r myrepo --all --json \
--jq "[.pullRequests[] | select(.source.branch.name == \"$BRANCH\")] | length")
if [ "$EXISTING" -eq 0 ]; then
bb pr create -w myworkspace -r myrepo \
-t "chore: $BRANCH" \
-b "Opened automatically by CI" \
-s "$BRANCH" \
-d main
fi

This script merges as soon as any reviewer has approved. To wait for all CI checks to pass first — the more common production pattern — see Auto-merge on green CI.

#!/bin/bash
# auto-merge.sh - Run in CI on schedule
set -e
WORKSPACE="myworkspace"
REPO="myrepo"
# --all is required: without it only the first 25 open PRs are considered
open_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --all --json --jq '.pullRequests[].id')
for pr_id in $open_prs; do
is_approved=$(bb pr view "$pr_id" -w "$WORKSPACE" -r "$REPO" --json \
--jq '[.participants[] | select(.approved == true)] | length > 0')
if [ "$is_approved" != "true" ]; then
sleep 1
continue
fi
if bb pr merge "$pr_id" -w "$WORKSPACE" -r "$REPO" --strategy squash --close-source-branch; then
echo "Merged PR #$pr_id"
else
echo "Could not merge PR #$pr_id (conflicts or failing checks)"
fi
sleep 2
done
generate-changelog.sh
#!/bin/bash
WORKSPACE="myworkspace"
REPO="myrepo"
SINCE_DATE=$(date -d '7 days ago' +%Y-%m-%d 2>/dev/null || \
date -v-7d +%Y-%m-%d)
echo "# Changelog - Week of $(date +%Y-%m-%d)"
echo ""
# External jq here because the program needs --arg; --jq takes no variables
bb pr list -w "$WORKSPACE" -r "$REPO" -s MERGED --all --json | jq -r --arg since "$SINCE_DATE" '
.pullRequests[] |
select(.updated_on >= $since) |
"- \(.title) (#\(.id)) by @\(.author.nickname // .author.display_name // "unknown")"
'

Every command exits 0 on success and 1 on any failure. There is no per-error exit code — the specific ErrorCode lives in the error payload.

Under --json, the error envelope is written to stderr as one compact line while stdout stays byte-clean JSON:

{"name":"BBError","code":1001,"message":"Authentication required. Run 'bb auth login' or set BB_USERNAME and BB_API_TOKEN."}

401, 403, and 404 failures carry an extra hint string. See Scripting & Automation and Error Codes.


Create a dedicated CI/CD token with minimal permissions:

Use case Required scopes
Verify identity read:user:bitbucket
Read-only checks + read:repository:bitbucket, read:pullrequest:bitbucket
Create/merge PRs + write:pullrequest:bitbucket
Report build statuses, write repository data + write:repository:bitbucket
Create repositories + admin:repository:bitbucket
Delete repositories + delete:repository:bitbucket

The + notation is cumulative for the read and write scopes only. admin:repository:bitbucket and delete:repository:bitbucket are separate scopes, not supersets of each other or of write:repository:bitbucket — grant each one you actually need. Full list: Token Scopes.

Store in Settings > Secrets and variables > Actions


✗ Authentication required. Run 'bb auth login' or set BB_USERNAME and BB_API_TOKEN.

Under --json the same failure appears on stderr as {"name":"BBError","code":1001,...}.

Check that both variables reach the job, and that the token has not expired:

Terminal window
echo "Username set: $([ -n "$BB_USERNAME" ] && echo 'yes' || echo 'no')"
echo "Token set: $([ -n "$BB_API_TOKEN" ] && echo 'yes' || echo 'no')"

bb auth login only uses the API-token path when BB_API_TOKEN is set (or when you pass -u/-p/--with-token). Otherwise it starts the OAuth browser flow on http://localhost:19872/callback and blocks for five minutes. Set BB_API_TOKEN in the step’s environment.

Add a delay between API calls:

Terminal window
for pr_id in $pr_ids; do
bb pr view "$pr_id" -w myworkspace -r myrepo --json
sleep 2
done

bun install -g puts binaries in ~/.bun/bin, which is not on PATH in a fresh shell. Export it before calling bb, in the same shell invocation:

Terminal window
export PATH="$HOME/.bun/bin:$PATH"
bb --version