Pipelines and CI: pipeline, deployment and commit status commands, CI/CD setup and retry recipes # Deployment Commands - Inspect Bitbucket Deployments > Reference for Bitbucket CLI deployment commands. List deployments, view a deployment's status and release, and list deployment environments from the command line. Inspect the deployments Bitbucket Pipelines records when a step declares a `deployment:` environment. The commands are read-only; trigger deployments with [`bb pipeline run`](/commands/pipeline/). Deployment commands operate at **repository** scope. Run them inside a cloned Bitbucket repository, or pass `-w, --workspace ` and `-r, --repo ` explicitly. All three subcommands accept the [global flags](/reference/global-flags/), including `--json [fields]` and `--jq `. *** ## `bb deployment list` ```bash bb deployment list [--limit ] [--all] ``` ### Examples ```bash bb deployment list bb deployment list --all # → { workspace, repoSlug, count, deployments } bb deployment list --json --jq '.deployments[].uuid' ``` ### Output ```text UUID ENVIRONMENT STATUS RELEASE COMMIT DATE -------------------------------------- ----------- ----------- ------- ------------ ------------ {3c6f1a2e-7b4d-4e8a-9f0c-1d2e3f4a5b6c} Production SUCCESSFUL #42 abcdef012345 8 months ago {8a9b0c1d-2e3f-4a5b-6c7d-8e9f0a1b2c3d} Staging IN_PROGRESS - - 8 months ago ``` ### Notes * Bitbucket’s deployment payloads reference environments by UUID only, so the table view fetches all environment pages to show names. If that lookup fails, it falls back to UUIDs. `--json` skips that call and returns the deployments exactly as the API sends them (`environment` is not enriched by the CLI). * `STATUS` is the completed result (`SUCCESSFUL`, `FAILED`, `STOPPED`) when there is one, otherwise the state (`IN_PROGRESS`, `UNDEPLOYED`). * `DATE` is when the deployment completed, else when it started, else the release creation time. * Rows come in the order Bitbucket returns them: the deployments endpoint documents no sort order and takes no sort parameter. On a repository with more than 25 deployments, pass `--all` (or a higher `--limit`) before concluding what is deployed where. *** ## `bb deployment view` View one deployment: environment, status, release, commit, deployer, and timing. ```bash bb deployment view ``` ### Examples ```bash bb deployment view "{3c6f1a2e-7b4d-4e8a-9f0c-1d2e3f4a5b6c}" # → { workspace, repoSlug, deployment } bb deployment view "{3c6f1a2e-7b4d-4e8a-9f0c-1d2e3f4a5b6c}" --json --jq '.deployment.state.name' ``` Quote the UUID: most shells treat `{...}` specially. *** ## `bb deployment environments` List the repository’s deployment environments. ```bash bb deployment environments [--limit ] [--all] ``` ### Examples ```bash bb deployment environments # → { workspace, repoSlug, count, environments } bb deployment environments --json --jq '.environments[].name' ``` Columns: name, environment type (`Test`, `Staging`, `Production`), and UUID. ### Related * [Pipeline Commands](/commands/pipeline/) * [Token Scopes](/reference/token-scopes/#deployments-bb-deployment-) # Pipeline Commands - Run and Inspect Bitbucket Pipelines > Reference for Bitbucket CLI pipeline commands. List, view, trigger, stop, and read logs of Bitbucket Pipelines builds from the command line. Manage Bitbucket Pipelines (CI/CD), list runs, inspect a run and its steps, trigger and stop runs, wait for a run to finish, and read or follow step logs. Pipeline commands operate at **repository** scope. Run them inside a cloned Bitbucket repository, or pass `-w, --workspace ` and `-r, --repo ` explicitly. See [Global flags](/reference/global-flags/) for JSON output, workspace/repository selection, and display options. An `` is either the build number from the UI (`42`) or the pipeline UUID with braces (`{a1b2c3d4-...}`). `view`, `watch` and `logs` take an optional `[id]`: without one they use the newest run on the current git branch, and ask for an id outside a git checkout or on a detached `HEAD`. `--json` output is wrapped in an envelope keyed by `workspace` and `repoSlug`; the `# →` comments below show each shape. *** ## `bb pipeline list` List pipeline runs, newest first. ```bash bb pipeline list [options] ``` ### Options | Option | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `--status ` | Filter by status: `PARSING`, `PENDING`, `PAUSED`, `HALTED`, `BUILDING`, `ERROR`, `PASSED`, `FAILED`, `STOPPED`, `UNKNOWN` (case-insensitive) | | `--branch ` | Filter by target branch | | `--sort ` | Sort attribute: `created_on`, `run_creation_date`, `creator.uuid`; prefix with `-` for descending (default: `-created_on`) | | `--limit ` | Maximum number of runs (default: 25) | | `--all` | List all runs (overrides `--limit`) | ### Examples ```bash bb pipeline list bb pipeline list --status FAILED bb pipeline list --branch main --limit 50 # → { workspace, repoSlug, [status], [branch], sort, count, pipelines } bb pipeline list --json # Build numbers of recent failures, via built-in --jq bb pipeline list --status FAILED --json --jq '.pipelines[].build_number' ``` ### Notes * Columns: build number, status (colored), ref (shortened to fit the terminal; disable with `--no-truncate`), trigger, created date (relative on a terminal), and duration for completed runs. * `--limit` is enforced across paginated responses. When results are capped the CLI prints `Showing 25 pipelines. Use --limit or --all to see more.` (suppressed with `--json`). * `--status` and `--sort` ignore case, so `failed` and `FAILED` both work. * An invalid value lists the allowed ones and, when the input is close to a valid one, suggests it: ```text --status must be one of: PARSING, PENDING, PAUSED, HALTED, BUILDING, ERROR, PASSED, FAILED, STOPPED, UNKNOWN (Did you mean FAILED?) ``` *** ## `bb pipeline view` View run details and a per-step summary. `[id]` is a build number or UUID (default: the newest run on the current branch). ```bash bb pipeline view [id] [options] ``` ### Examples ```bash # Newest run on the current branch bb pipeline view bb pipeline view 42 bb pipeline view {a1b2c3d4-0000-0000-0000-000000000000} # → { workspace, repoSlug, pipeline, steps } bb pipeline view 42 --json # Status name of the run bb pipeline view 42 --json --jq '.pipeline.state.result.name // .pipeline.state.name' ``` ### Notes * The human view shows number, UUID, status, ref, the custom pipeline selector when the run used one (`Pipeline:`), trigger, creator, created/completed timestamps, duration, and a step table (index, name, status, duration). * The step index is the value you pass to `bb pipeline logs --step`. * The step table and the JSON `steps` array are complete even on large runs; the CLI follows the steps endpoint’s pagination. * An unknown id returns `Pipeline 42 not found in acme/api.` *** ## `bb pipeline run` Trigger a pipeline run on a branch. ```bash bb pipeline run [options] ``` ### Options | Option | Description | | ----------------------- | --------------------------------------------------------------------------------------------- | | `-b, --branch ` | Branch to run on (default: current git branch) | | `--commit ` | Run against a specific commit on the branch | | `-p, --pipeline ` | Custom pipeline definition from `bitbucket-pipelines.yml` | | `--var ` | Pipeline variable; repeatable, value may contain `=` | | `--dry-run` | Print the write request instead of sending it ([details](/reference/global-flags/#--dry-run)) | ### Examples ```bash # Run the default pipeline for the current branch bb pipeline run bb pipeline run --branch main bb pipeline run --pipeline deploy-prod --var ENV=prod --var DRY_RUN=false bb pipeline run --branch main --commit abc123def456 # → { workspace, repoSlug, pipeline }; grab the build number bb pipeline run --branch main --json --jq '.pipeline.build_number' ``` ### Notes * Outside a git repository, `--branch` is required (the error tells you so). * `--pipeline ` selects a `custom:` pipeline defined in `bitbucket-pipelines.yml`. * Variables are sent unsecured; secured variables must be configured in repository settings. * On success the CLI prints the build number plus ready-to-paste `bb pipeline view` / `bb pipeline logs` commands. *** ## `bb pipeline stop` Stop a running pipeline. `` is a build number or UUID. ```bash bb pipeline stop [options] ``` ### Options | Option | Description | | ----------- | --------------------------------------------------------------------------------------------- | | `--dry-run` | Print the write request instead of sending it ([details](/reference/global-flags/#--dry-run)) | ### Examples ```bash bb pipeline stop 42 # → { workspace, repoSlug, pipelineId, stopped } bb pipeline stop 42 --json --jq '.stopped' ``` ### Notes * No `--yes` confirmation is required: stopping CI is reversible, rerun with `bb pipeline run`. *** ## `bb pipeline watch` Wait for a run to finish, printing each step’s status as it changes. Exits non-zero unless the run passes. `[id]` is a build number or UUID (default: the newest run on the current branch). ```bash bb pipeline watch [id] [options] ``` ### Options | Option | Description | | ---------------------- | --------------------------------------------- | | `--interval ` | Seconds between status checks (default: `10`) | ### Examples ```bash # Newest run on the current branch bb pipeline watch # Trigger, then block until the run passes bb pipeline run && bb pipeline watch # → { workspace, repoSlug, pipeline, steps }, same as `pipeline view` bb pipeline watch 42 --json --jq '.pipeline.state.result.name' ``` Output: ```text Watching pipeline #42 on main 1. Build: PENDING 1. Build: IN_PROGRESS 2. Test: PENDING 1. Build: SUCCESSFUL 2. Test: IN_PROGRESS 2. Test: SUCCESSFUL ✓ Pipeline #42 SUCCESSFUL in 3m 12s ``` ### Notes * A failed, stopped, errored or expired run exits `1` with error code [`10001` CI\_FAILED](/reference/error-codes/#10001---ci_failed), after the final state is printed. * A run paused on a manual step also ends the watch with `10001` (`Pipeline #42 is paused, waiting for a manual step.`): it has not passed, and it will not move until someone triggers the step. * `--json` prints nothing while waiting, then the final `{ workspace, repoSlug, pipeline, steps }` envelope. A failed run still exits `1`, with the error envelope on stderr. * Each poll makes at least two API calls (more for runs with over 10 steps). The 10 second default keeps a long watch well inside Bitbucket’s hourly rate limit. *** ## `bb pipeline logs` Print the raw log of a pipeline step. `[id]` is a build number or UUID (default: the newest run on the current branch). ```bash bb pipeline logs [id] [options] ``` ### Options | Option | Description | | ---------------------------- | --------------------------------------------------------------------- | | `-s, --step ` | Step to fetch: a step UUID (braces optional) or a 1-based index | | `-f, --follow` | Stream new output until the run finishes (every step unless `--step`) | | `--interval ` | Seconds between log checks with `--follow` (default: `10`) | ### Examples ```bash # Single-step runs need no --step bb pipeline logs 42 bb pipeline logs 42 --step 2 bb pipeline logs 42 --step {a1b2c3d4-0000-0000-0000-000000000000} bb pipeline logs 42 --step a1b2c3d4-0000-0000-0000-000000000000 # → { workspace, repoSlug, pipelineId, stepUuid, log } bb pipeline logs 42 --json --jq '.log' # Stream every step of the newest run on this branch as it runs bb pipeline logs --follow # Stream one step; stops when that step finishes bb pipeline logs 42 --follow --step 2 ``` ### Notes * With exactly one step, it is selected automatically. * With several steps and no `--step`, the CLI lists the steps (index, name, status, UUID) instead of guessing. With `--json` it returns `{ workspace, repoSlug, pipelineId, count, steps }` so scripts and agents can pick a step UUID and call again. * Every step is selectable even on large runs, the CLI follows the steps endpoint’s pagination, so indexes and UUIDs beyond the API’s default page size of 10 work. * The log is printed verbatim to stdout, so it pipes cleanly into `grep` or a file. On a terminal it opens in your pager (`less -FRX` unless [`BB_PAGER` or `PAGER`](/reference/environment-variables/#pager) says otherwise). * `--follow` waits for a queued run to get steps, then prints each step’s log in order, fetching only new bytes on each poll. With several steps, each one starts with a `==> Step 2: Test` header; parallel steps are printed one after another, not interleaved. It stops when the run finishes or pauses on a manual step, or, with `--step`, when that step finishes. It never opens the pager, so output appears as it is written. Use `bb pipeline watch` for the exit status. * `--follow` cannot be combined with `--json`; use `bb pipeline watch --json` for the final state. * Four failure modes, a queued run with no steps yet, an out-of-range `--step` index, a `--step` UUID that matches nothing, and a step that has produced no log yet, each get their own message, in that order: ```text Pipeline 42 has no steps yet. It may still be queued — check with `bb pipeline view 42`. --step index 9 is out of range; the pipeline has 3 steps. No step matching 'abc' found. Available steps: 1 ({uuid-1}), 2 ({uuid-2}). No log found for step {uuid-2} of pipeline 42. The step may not have started yet. ``` *** ## See also * [Scripting & Automation](/guides/scripting/), JSON envelopes, `--jq`, exit codes. * [CI/CD Integration](/guides/cicd/), using `bb` inside pipelines. # Status Commands - Commit Build Statuses > Reference for Bitbucket CLI status commands. List build statuses on a commit and report CI build results (INPROGRESS, SUCCESSFUL, FAILED) from scripts and pipelines. Manage build statuses on commits, the red/green/yellow indicators Bitbucket shows next to commits and pull requests. `bb status set` is built for CI scripts: it is non-interactive, idempotent per status key, and JSON-friendly. Status commands operate at **repository** scope. Run them inside a cloned Bitbucket repository, or pass `-w, --workspace ` and `-r, --repo ` explicitly. See [Global flags](/reference/global-flags/) for JSON output, workspace/repository selection, and display options. A `` is a full 40-character hash or any abbreviated prefix (`abc1234`). `--json` output is wrapped in an envelope keyed by `workspace` and `repoSlug`; the `# →` comments below show each shape. *** ## `bb status list` List the build statuses reported on a commit. ```bash bb status list [options] ``` ### Options | Option | Description | | ------------------ | ---------------------------------------- | | `--limit ` | Maximum number of statuses (default: 25) | | `--all` | List all statuses (overrides `--limit`) | ### Examples ```bash bb status list abc1234 bb status list abc1234 --all # → { workspace, repoSlug, commit, count, statuses } bb status list abc1234 --json # States only, via built-in --jq bb status list abc1234 --json --jq '.statuses[].state' ``` ### Notes * Columns: status key, state (colored: green `SUCCESSFUL`, red `FAILED`, yellow `INPROGRESS`, gray `STOPPED`), name, description (name and description shrink to fit the terminal; disable with `--no-truncate`), and URL. * `bb pr checks` colors states the same way but renders a different table: `STATUS / NAME / DESCRIPTION / UPDATED`, with the state shown as `OK passed` / `FAIL failed` / `RUN running` / `STOP stopped` rather than the raw state string. * An unknown sha returns `Commit abc1234 not found in acme/api.` *** ## `bb status set` Create or update a build status on a commit. `` is a full or abbreviated hash. ```bash bb status set --key --state [options] ``` ### Options | Option | Description | | ----------------------------- | ------------------------------------------------------------------------------------------------------------ | | `--key ` | **Required.** Unique status key, e.g. `BB-DEPLOY`. Re-running with the same key updates the existing status. | | `--state ` | **Required.** One of `FAILED`, `INPROGRESS`, `STOPPED`, `SUCCESSFUL` (case-insensitive) | | `--url ` | Link back to the build system | | `--name ` | Build identifier, e.g. `BB-DEPLOY-1` | | `--description ` | Short build description | | `--refname ` | Ref the build ran on, e.g. a branch name | | `--dry-run` | Print the write request instead of sending it ([details](/reference/global-flags/#--dry-run)) | ### Examples ```bash bb status set abc1234 --key CI --state INPROGRESS bb status set abc1234 --key CI --state SUCCESSFUL --url https://ci.example.com/builds/42 bb status set abc1234 --key LINT --state FAILED --description "ESLint found 3 errors" --refname main # → { workspace, repoSlug, commit, status } bb status set abc1234 --key CI --state SUCCESSFUL --json --jq '.status.state' ``` #### CI recipe: wrap a build in INPROGRESS → SUCCESSFUL/FAILED ```bash #!/usr/bin/env bash set -euo pipefail SHA="$(git rev-parse HEAD)" KEY="CI" URL="https://ci.example.com/builds/${BUILD_ID:-local}" # Mark the commit as building before the work starts bb status set "$SHA" --key "$KEY" --state INPROGRESS --url "$URL" \ --name "CI Build ${BUILD_ID:-local}" --refname "$(git branch --show-current)" # Run the build; report the result either way if ./run-build.sh; then bb status set "$SHA" --key "$KEY" --state SUCCESSFUL --url "$URL" else bb status set "$SHA" --key "$KEY" --state FAILED --url "$URL" \ --description "Build failed; see logs" exit 1 fi ``` ### Notes * **Idempotent per status key:** the CLI first POSTs a new status. If the POST is rejected for anything other than an auth or not-found failure, most commonly because a status with that key already exists on the commit, the normal case on CI re-runs, it retries as a PUT against the existing key. Repeated `bb status set` calls with the same `--key` are therefore always safe. * `--state` is upper-cased before validation, so `successful` and `SUCCESSFUL` both work. An invalid value lists the allowed states and, when the input is close to a valid one, suggests it: ```text --state must be one of: FAILED, INPROGRESS, STOPPED, SUCCESSFUL (Did you mean SUCCESSFUL?) ``` * On success the CLI prints `✓ Status set to on `; with `--json` it returns the resulting status resource wrapped in `{ workspace, repoSlug, commit, status }`. * These statuses are what `bb pr checks` aggregates for a pull request. *** ## See also * [Commit Commands](/commands/commit/), list and inspect the commits themselves. * [CI/CD Integration](/guides/cicd/), using `bb` inside pipelines. * [Scripting & Automation](/guides/scripting/), JSON envelopes, `--jq`, exit codes. # CI/CD integration > Install and authenticate Bitbucket CLI in GitHub Actions, GitLab CI, Bitbucket Pipelines, Jenkins, and Azure DevOps. The Bitbucket CLI automates pull request (PR) workflows, reports build statuses back to commits, and triggers Bitbucket Pipelines from any CI system. ## Quick setup 1. **Store credentials as secrets** in your CI/CD platform. * `BB_USERNAME`, your Atlassian account email * `BB_API_TOKEN`, your Bitbucket API token 2. **Install the CLI.** The standalone binary needs no Bun, Node.js or npm, so the job skips the runtime install. Verify it against the release’s `SHA256SUMS` before running it: ```bash base=https://github.com/0pilatos0/bitbucket-cli/releases/latest/download curl -fsSLO "$base/bb-linux-x64" curl -fsSLO "$base/SHA256SUMS" sha256sum --check --ignore-missing SHA256SUMS install -D -m 755 bb-linux-x64 "$HOME/.local/bin/bb" export PATH="$HOME/.local/bin:$PATH" ``` Replace `latest/download` with `download/v` to pin a release, and `bb-linux-x64` with `bb-linux-arm64` on arm64 agents. See [Installation](/getting-started/installation/#standalone-binary) for the other platforms and for `gh attestation verify`. The binary needs glibc, so on Alpine use the Bun route below. To use the npm package instead, install Bun first. The package is Bun-only; it exits immediately under Node.js. ```bash 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. ```bash bb auth login ``` 4. **Run commands** with explicit workspace and repository flags. ```bash bb pr list -w myworkspace -r myrepo --json ``` ### Paging List commands return 25 items by default. Pass `--limit ` to raise or lower that cap, or `--all` to fetch every page. `--all` overrides `--limit`. ```bash bb pr list -w myworkspace -r myrepo --all --json ``` See [Global Flags](/reference/global-flags/#list-command-flags). ### Reading JSON without an external jq `--json [fields]` and `--jq ` 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`. ```bash # 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 ` drops the envelope on list commands, `count` disappears once you project. See [Scripting & Automation](/guides/scripting/#field-selection-and---jq). *** ## Platform examples Each snippet shows the platform-specific part: how secrets reach the job, and how the CLI gets on `PATH`. The GitHub Actions example uses the standalone binary and the others use Bun; either install from [Quick setup](#quick-setup) works on any of them. The `bb auth login` step is the same everywhere. ### GitHub Actions ```yaml name: Bitbucket PR Check on: push: branches: [main] pull_request: jobs: check-bitbucket-prs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Bitbucket CLI run: | base=https://github.com/0pilatos0/bitbucket-cli/releases/latest/download curl -fsSLO "$base/bb-linux-x64" curl -fsSLO "$base/SHA256SUMS" sha256sum --check --ignore-missing SHA256SUMS install -D -m 755 bb-linux-x64 "$HOME/.local/bin/bb" echo "$HOME/.local/bin" >> "$GITHUB_PATH" - 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 --all --json --jq '.count' ``` Appending to `$GITHUB_PATH` puts `bb` on `PATH` for every later step. ### GitLab CI ```yaml 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. ### Bitbucket Pipelines ```yaml 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`. Caution Pipelines does **not** inject Bitbucket credentials, and the CLI never reads the `BITBUCKET_*` variables for auth. Add both `BB_USERNAME` and `BB_API_TOKEN` as secured repository or workspace variables. Without `BB_API_TOKEN`, `bb auth login` falls back to the OAuth browser flow and the step hangs until it times out. ### Jenkins ```groovy pipeline { agent any environment { BB_USERNAME = credentials('bitbucket-account-email') 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 --all --json --jq '.count' ''' } } } } ``` Each `sh` step is a fresh shell, so `PATH` has to be exported in every one. To automate merges from Jenkins, use the [checked merge recipe](/recipes/auto-merge-on-ci-green/). ### Azure DevOps ```yaml 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. *** ## Common use cases ### Report build status back to Bitbucket `bb status set ` 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. ```bash 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 `. ### Trigger a Bitbucket pipeline from another CI system ```bash # 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 watch` waits for the latest run on the current branch and exits non-zero unless it passes; `bb pipeline logs --follow` streams its logs. `bb pipeline list --status FAILED`, `bb pipeline view `, `bb pipeline logs --step `, and `bb pipeline stop ` cover the rest. See [bb pipeline](/commands/pipeline/). ### Create a PR from a branch Run this in a branch build after pushing the source branch. It uses external `jq` to pass the branch name as data, and lists every open PR before creating one: ```bash set -euo pipefail BRANCH=${GITHUB_REF#refs/heads/} EXISTING=$(bb pr list -w myworkspace -r myrepo --all --json | \ jq --arg branch "$BRANCH" '[.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 ``` Use your platform’s source-branch variable outside GitHub Actions. A concurrent job can create the same PR between the lookup and create call; handle that failure in your job rather than assuming the lookup is a lock. ### Merge approved PRs automatically Use [Auto-merge on green CI](/recipes/auto-merge-on-ci-green/). It reads every build-status page, checks required status keys, and rechecks the PR head before merging. Repository merge policies must enforce your authoritative requirements. One approval alone does not establish that a PR is ready to merge. ### Generate a changelog from merged PRs See [Reporting and analytics](/recipes/reporting-analytics/) for paginated PR reports and diff statistics. ## Exit codes and errors Commands exit `0` on success and `1` on failure. Interrupted interactive prompts exit `130`; disable prompts in automation with `--no-input`. Set `BB_DETAILED_EXIT_CODES=1` to tell usage (`2`), not found (`3`), authentication (`4`) and confirmation required (`5`) apart. In `--json` mode, successful JSON goes to stdout and command errors, argument-parsing errors included, go to stderr as JSON. HTTP traces from `BB_DEBUG` also go to stderr, so stdout stays parseable. See [Scripting and automation](/guides/scripting/#error-handling) for an error handling example and [Error codes](/reference/error-codes/) for numeric codes. ## Security considerations ### Token scopes Create a dedicated CI token with only the permissions its commands require. Use the [scope reference](/reference/token-scopes/) rather than copying a broad scope list. Grant read, write, admin, and delete scopes separately as needed. ### Secrets management * GitHub Actions Store in **Settings > Secrets and variables > Actions** * GitLab Store in **Settings > CI/CD > Variables** (masked, protected) * Jenkins Use **Credentials** plugin with Secret text type * Bitbucket Pipelines Store as **secured** repository or workspace variables *** ## Troubleshooting CI/CD ### Authentication fails ```text ✗ Authentication required. Run 'bb auth login'. ``` Under `--json` the same failure appears on stderr as `{"name":"BBError","code":1001,...}`. Check that both variables reach the job, that `BB_USERNAME` is the Atlassian account email, and that the token has not expired: ```bash echo "Account email set: $([ -n "$BB_USERNAME" ] && echo 'yes' || echo 'no')" echo "Token set: $([ -n "$BB_API_TOKEN" ] && echo 'yes' || echo 'no')" ``` ### The auth step hangs `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. ### Rate limiting in loops Add a delay between API calls: ```bash for pr_id in $pr_ids; do bb pr view "$pr_id" -w myworkspace -r myrepo --json sleep 2 done ``` ### `bb: command not found` `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: ```bash export PATH="$HOME/.bun/bin:$PATH" bb --version ``` # Auto-merge when CI is green > Poll every build-status page, require named checks, and recheck the PR head before requesting a merge. This recipe requires an approval and an explicit list of CI check keys. It reads every status page with `bb api --paginate`, then checks that the PR head still matches the commit inspected. Caution Configure Bitbucket’s required checks and approval rules on the destination branch before using this script. A head or status can change between the final read and the merge request. The CLI cannot make these operations atomic; server-side merge policies must enforce the actual merge requirements. ## What “all-green” means Each required key must have exactly one status with `state: SUCCESSFUL`. Every returned status must also be successful. Missing checks, zero checks, duplicate keys, `INPROGRESS`, `STOPPED` and unknown states never permit a merge. A failed check aborts the run. Set `REQUIRED_CHECK_KEYS` to the exact keys your CI publishes, separated by spaces. These are status keys, not display names. Keep keys free of whitespace for this script. `bb pr checks 42 --watch` waits for running checks and exits non-zero when one failed or stopped, but it does not know which keys you require, so this recipe reads the statuses itself. ### jq filter for all-green ```bash bb api "/repositories/$WORKSPACE/$REPO/pullrequests/42/statuses" --paginate --json \ | jq --arg keys 'build test' ' .values as $statuses | ($keys | split(" ")) as $required | ($statuses | length) > 0 and all($statuses[]; .state == "SUCCESSFUL") and ([$statuses[].key] | length == (unique | length)) and all($required[]; . as $key | [$statuses[] | select(.key == $key)] | length == 1) ' ``` ## Recipe: poll-then-merge Save as `auto-merge-on-green.sh`. It exits `0` after a successful merge, `1` on errors or timeout, and `2` when `--once` finds a PR that is not ready. ```bash #!/bin/bash set -euo pipefail WORKSPACE="${WORKSPACE:?set WORKSPACE}" REPO="${REPO:?set REPO}" PR_ID="${1:?Usage: $0 [--once]}" REQUIRED_CHECK_KEYS="${REQUIRED_CHECK_KEYS:?set required CI check keys}" TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-1800}" POLL_INTERVAL="${POLL_INTERVAL:-30}" once="${2:-}" path="/repositories/$WORKSPACE/$REPO/pullrequests/$PR_ID" required=$(printf '%s' "$REQUIRED_CHECK_KEYS" | jq -Rc 'split(" ") | map(select(length > 0)) | unique') jq -e 'length > 0' <<< "$required" >/dev/null deadline=$(( $(date +%s) + TIMEOUT_SECONDS )) while true; do pr=$(bb api "$path" --json) [ "$(jq -r '.state' <<< "$pr")" = OPEN ] || exit 2 head=$(jq -er '.source.commit.hash | select(type == "string" and length > 0)' <<< "$pr") statuses=$(bb api "$path/statuses" --paginate --json) jq -e '.values | type == "array"' <<< "$statuses" >/dev/null if jq -e 'any(.values[]; .state == "FAILED")' <<< "$statuses" >/dev/null; then echo "PR #$PR_ID has a failed check." >&2 exit 1 fi if jq -e --argjson required "$required" ' .values as $statuses | ($statuses | length) > 0 and all($statuses[]; .state == "SUCCESSFUL") and ([$statuses[].key] | length == (unique | length)) and all($required[]; . as $key | [$statuses[] | select(.key == $key)] | length == 1) ' <<< "$statuses" >/dev/null; then current=$(bb api "$path" --json) if jq -e --arg head "$head" ' .state == "OPEN" and .source.commit.hash == $head and any(.participants[]?; .approved == true) ' <<< "$current" >/dev/null; then bb pr merge "$PR_ID" -w "$WORKSPACE" -r "$REPO" \ --strategy squash --close-source-branch exit 0 fi fi [ "$once" != --once ] || exit 2 if [ "$(date +%s)" -ge "$deadline" ]; then echo "Timed out waiting for PR #$PR_ID checks and approval." >&2 exit 1 fi sleep "$POLL_INTERVAL" done ``` ### Usage ```bash WORKSPACE=myworkspace REPO=myrepo REQUIRED_CHECK_KEYS="build test" \ ./auto-merge-on-green.sh 42 ``` ## Recipe: scan all open PRs Save the script above alongside this one. Run it on a schedule to attempt each ready PR once. A failed check, API error or rejected merge makes the scan exit nonzero after processing the remaining PRs. ```bash #!/bin/bash set -euo pipefail WORKSPACE="${WORKSPACE:?set WORKSPACE}" REPO="${REPO:?set REPO}" export WORKSPACE REPO export REQUIRED_CHECK_KEYS="${REQUIRED_CHECK_KEYS:?set required CI check keys}" script_dir=$(cd "$(dirname "$0")" && pwd) open_prs=$(bb pr list -w "$WORKSPACE" -r "$REPO" --all --json --jq '.pullRequests[].id') failed=0 for pr_id in $open_prs; do if "$script_dir/auto-merge-on-green.sh" "$pr_id" --once; then echo "Merged PR #$pr_id" else status=$? if [ "$status" -eq 2 ]; then echo "PR #$pr_id: not ready" else echo "PR #$pr_id: check or merge failed" >&2 failed=1 fi fi sleep 2 done exit "$failed" ``` Both scripts need Bash and external jq. `--all` makes the PR scan include older open PRs rather than only the default first 25. ## Related * [`bb pr checks`](/commands/pr/activity-and-checks/#bb-pr-checks) * [`bb pr merge`](/commands/pr/review-and-merge/) * [Scripting and automation](/guides/scripting/) # Retry wrapper for transient failures > A small shell wrapper that retries bb invocations with exponential backoff when the built-in retry isn't enough. The CLI retries HTTP 429, 502, 503 and 504 up to three times with exponential backoff. GET, HEAD and OPTIONS also retry transient network errors `ECONNRESET`, `ETIMEDOUT`, `ECONNABORTED`, `EAI_AGAIN` and `EPIPE`. HTTP and network retries share that three-retry budget. DNS `ENOTFOUND`, connection refusal and certificate/TLS errors are not retried. Fix the address, connectivity or certificate configuration rather than adding attempts. Use a wrapper when a read-only batch needs a longer retry window. See [built-in resilience](/guides/scripting/#built-in-resilience) for the CLI behavior. ## What you’ll see when retry kicks in Each built-in retry announces itself on stderr; `⚠ Rate limited, retrying in 1.0s (attempt 1/3)...`; unless you passed `--json`, which suppresses the chatter so it can’t pollute a structured pipeline. After the initial request and up to three retries fail, the CLI prints Bitbucket’s own message to stderr, prefixed with a red `✗` (or `ERR `under `--no-unicode`). For a rate limit that is typically: ```text ✗ Rate limit for this resource has been exceeded ``` The HTTP status number does not appear in text mode. To branch on it, pass `--json`: the error envelope goes to stderr as a single compact line. ```json { "name": "APIError", "code": 2004, "message": "Rate limit for this resource has been exceeded", "context": { "status": 429 }, "statusCode": 429, "response": { "type": "error", "error": { "message": "Rate limit for this resource has been exceeded" } } } ``` The envelope is flat; there is no `error` wrapper object; and `code` is a number from the [Error Codes reference](/reference/error-codes/), here `2004` (`API_RATE_LIMITED`). `statusCode` and `response` appear on API errors only; a validation or config error has neither, and `context` on an API error also carries `method` and `url` (trimmed above). A `hint` string is added for 401, 403 and 404 responses; a 429 gets no hint. The wrappers buffer stdout and publish it only after a successful attempt. Failed attempts cannot leave partial JSON in the report file. stderr still reports retry progress. Caution **Don’t blanket-retry mutating commands.** `bb pr create`, `bb pr merge`, `bb pr approve`, and similar can succeed *server-side* even when the response read fails. A retry might create a duplicate pull request (PR) or re-approve. Limit the wrapper below to read-only commands or commands you’ve made idempotent (e.g., check existence before creating). ## Recipe: bb-with-retry wrapper ```bash #!/bin/bash # bb-retry.sh - Run a bb command with exponential backoff. # # Usage: # ./bb-retry.sh pr list -w myworkspace -r myrepo --json # # Env vars: # MAX_ATTEMPTS default 5 # INITIAL_DELAY default 2 (seconds) # MAX_DELAY default 60 (seconds) set -euo pipefail MAX_ATTEMPTS="${MAX_ATTEMPTS:-5}" INITIAL_DELAY="${INITIAL_DELAY:-2}" MAX_DELAY="${MAX_DELAY:-60}" output_file=$(mktemp) trap 'rm -f "$output_file"' EXIT attempt=1 delay="$INITIAL_DELAY" while true; do # Capture the failed command status before another command replaces it. bb "$@" > "$output_file" && { cat "$output_file"; exit 0; } exit_code=$? if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then echo "bb failed after $attempt attempts (exit $exit_code)" >&2 exit "$exit_code" fi # Add jitter: ±25% of the current delay jitter=$(( RANDOM % (delay / 2 + 1) - delay / 4 )) sleep_time=$(( delay + jitter )) [ "$sleep_time" -lt 1 ] && sleep_time=1 echo "bb failed (exit $exit_code); retry $((attempt + 1))/$MAX_ATTEMPTS in ${sleep_time}s" >&2 sleep "$sleep_time" attempt=$(( attempt + 1 )) delay=$(( delay * 2 )) [ "$delay" -gt "$MAX_DELAY" ] && delay="$MAX_DELAY" done ``` ### Usage ```bash chmod +x bb-retry.sh # Read-only: successful output goes to the report file. ./bb-retry.sh pr list -w myworkspace -r myrepo --json > prs.json # Tune the cap for big jobs. MAX_ATTEMPTS=10 INITIAL_DELAY=5 ./bb-retry.sh pr view 42 --json ``` ## Recipe: retry only on a specific error code To retry rate limits but *not* authentication failures, run the command with `--json` and read `statusCode` off the error envelope. Do not grep the text-mode message for `429`: that message is Bitbucket’s own prose and usually contains no digits at all, while any PR title containing “502” would match. bb-retry-on-rate-limit.sh ```bash #!/bin/bash # # Usage: # ./bb-retry-on-rate-limit.sh pr list -w myworkspace -r myrepo # # Appends --json so the error envelope is machine-readable. set -euo pipefail MAX_ATTEMPTS=5 output_file=$(mktemp) err_file=$(mktemp) trap 'rm -f "$output_file" "$err_file"' EXIT attempt=1 delay=2 while true; do bb "$@" --json > "$output_file" 2> "$err_file" && { cat "$output_file" cat "$err_file" >&2 exit 0 } err_msg=$(cat "$err_file") # statusCode exists on API errors only; empty for validation/config errors. status=$(printf '%s' "$err_msg" | jq -r '.statusCode // empty' 2>/dev/null || true) # Retry only on rate-limit / transient gateway errors. case "$status" in 429|502|503|504) if [ "$attempt" -ge "$MAX_ATTEMPTS" ]; then echo "$err_msg" >&2 exit 1 fi echo "HTTP $status, retrying in ${delay}s..." >&2 sleep "$delay" delay=$(( delay * 2 )) attempt=$(( attempt + 1 )) ;; *) # Non-transient (or no statusCode at all): fail fast. echo "$err_msg" >&2 exit 1 ;; esac done ``` ## When *not* to write your own retry The first wrapper retries every nonzero exit, including permanent errors. Prefer the status-filtered wrapper for unattended jobs. Neither wrapper fixes: * **Auth failures**; `Authentication required. Run 'bb auth login'.` (`code: 1001`). Retrying won’t help; fix the credentials. * **404 / not-found errors** (`code: 2002`); these don’t get more found over time. * **Validation errors** (`code: 5001` or `5002`, e.g. `Option --title is required`); bugs in your script, not transient. The CLI exits `1` for all of these, same as for transient errors. Branch on the JSON envelope instead. Use `jq -r '.statusCode // .code'`: `statusCode` is present on API errors, and `code` covers the rest. ## Related * [Built-in resilience](/guides/scripting/#built-in-resilience) * [Error codes reference](/reference/error-codes/) * [JSON output reference](/reference/json-output/)