Skip to content
buildkitePublic

About

A command line interface for Buildkite.

Resources

Contributing

Stars

206 stars

Watchers

20 watching

Forks

Latest commit

 

History

833 Commits

Folders and files

Repository files navigation

bk - The Buildkite CLI

Latest Release

A command line interface for Buildkite.

Full documentation is available at buildkite.com/docs/platform/cli.

Quick Start

Install

brew tap buildkite/buildkite && brew install buildkite/buildkite/bk

Or download a binary from the releases page.

To update a standalone release-binary install later, run:

bk update

If bk is managed by Homebrew or mise, bk update will tell you how to update it with that tool instead.

Authenticate

bk auth login

Stored credentials are selected per organization. When targeting another organization, authenticate it explicitly:

bk auth login --org other-org

Logging in also selects that organization in your user-wide configuration. Repository configuration and BUILDKITE_ORGANIZATION_SLUG still take precedence. Use the global --org flag to select an organization for one invocation without changing either configuration file. It overrides BUILDKITE_ORGANIZATION_SLUG and repository/user defaults, and selects that organization's stored credentials:

bk build list --org other-org
bk build view 123 --pipeline my-pipeline --org other-org
bk --org other-org job list
bk agent list --org other-org

The flag is available to all commands, including cluster, queue, secret, team, cache registry, repository connection, package, and API commands. Authentication and configuration commands retain their existing credential/selection changes. Pipeline selections discovered from the repository are not cached when using --org or BUILDKITE_ORGANIZATION_SLUG, so a temporary organization cannot overwrite the saved pipeline defaults. Existing cached pipeline names are only used when the effective organization matches the saved organization; otherwise provide --pipeline or let repository discovery find a pipeline in the target org.

A qualified pipeline or build target (including a URL) selects its organization for the invocation in the same way as --org, including that organization's stored credentials. Omit --org when the target already specifies the desired organization; when both are given, or two targets name different organizations, the conflict produces an error before execution:

bk build list --pipeline other-org/my-pipeline
bk build view 123 --pipeline other-org/my-pipeline
bk build view https://buildkite.com/other-org/my-pipeline/builds/123
bk job list --pipeline https://buildkite.com/other-org/my-pipeline --build 123

For pipeline copy, --org selects the source organization; --target org/name can still specify a different destination organization. A qualified source such as bk pipeline copy other-org/their-pipeline --target my-copy reads from other-org but still creates the copy in the selected organization. bk browse builds its URL from the target without calling the API, so it does not require credentials for the target's organization.

The CLI does not fall back to another organization's stored credentials. For automation or a token with access to multiple organizations, supply BUILDKITE_API_TOKEN explicitly. This environment token takes precedence over stored credentials and is not replaced by stored OAuth credentials if a request fails authentication.

Create a pipeline

From a Git checkout, bk pipeline create infers the repository URL and default name from origin (or uses the checkout directory name if no origin exists). An explicit name and --repository override those defaults. When used interactively without a name or repository argument, it offers a recommended local starter and alternatives if no pipeline file exists:

Guided creation defaults to Pipeline created: <URL> on stdout, while file status and starter prompts go to stderr. Format flags and explicitly configured output preferences take precedence; named and scripted output defaults are unchanged.

bk pipeline create
bk pipeline create "My Pipeline" --repository git@github.com:org/repo.git
bk pipeline create --no-input --template go
bk pipeline create --dry-run --json

Named invocations, redirected input and --no-input do not add prompts or local files unless --template is supplied. Dry-run resolves the same name and URL but never writes local files or creates a pipeline/webhook; it still performs API reads and requires the existing authentication configuration.

bk init uses the same starter selection without creating a remote pipeline. Without interactive input it uses the project recommendation; --template selects go, node, pipenv, ruby or empty explicitly. Both commands search the checkout root, including from subdirectories and linked worktrees, and leave existing pipeline files unchanged.

Starters are bundled adaptations of Buildkite's Go, Node, Pipenv and Ruby examples. Go runs test/vet; Node requires a real test script and adapts installation to npm, pnpm, Yarn or Bun lockfiles. Pipenv/pytest and Bundler/RSpec recommendations require dependency evidence and lockfiles. Agents must have the matching runtime and package manager installed; these starters do not provision agents, Docker, hooks or caches. The empty starter contains steps: [] and needs editing.

Review generated files, then commit and push them before building. Remote builds cannot see local starters; creating a pipeline does not publish them.

Compare builds

bk build compare 123 --pipeline my-pipeline --json
bk build compare acme/my-pipeline/123 --baseline 120 --text
bk build compare https://buildkite.com/acme/my-pipeline/builds/123 --yaml
# Inspect newly failing target jobs without interpreting every unchanged row:
bk build compare acme/my-pipeline/123 --json |
  jq '.steps[] | select(.change == "newly_failing") | .target | {id, web_url, retries_count}'

Requires read_builds. The required target accepts the usual build selectors; plain numbers use --pipeline or configuration/repository inference. JSON/YAML never prompts for pipeline selection; specify --pipeline when ambiguous. Configured/environment no_input and quiet settings are preserved alongside the global CLI flags, including when selecting another organization's credentials. --baseline is a different positive build number in the same pipeline. It may be newer, failed or on another branch (cross-branch comparisons warn). Without it, search at most five pages/500 candidates in API newest-first order for a lower-numbered, strictly earlier-created build on the exact target branch that is currently passed. It need not have passed before the target started. Missing branch/creation time or no eligible baseline is an error: supply --baseline. Missing/denied builds, failed pages, and inventories exceeding ten pages/1000 jobs per build also fail with no partial output, not false additions.

Only final attempts (retried=false, including running attempts) are compared. Identity includes step key, type, matrix and parallel index and total, keeping absent coordinates distinct from zero. Keyed renames/group moves still match. Unkeyed jobs use an exact name plus group, type, matrix and parallel coordinates, reported as match_method: name_fallback with a heuristic warning. Keys never match names. Duplicate identities on either side leave all affected jobs on both sides unmatched; unnamed unkeyed jobs also remain unmatched. Renames of unkeyed jobs and changed coordinates appear as added/removed, not proven changes to the same step. Unfinished builds warn: added/removed is snapshot presence only.

JSON/YAML contains organization, pipeline, target/baseline metadata (ID, number, state, branch, commit, URL, creation time), baseline_selection, candidates_scanned, final-attempt target_jobs/baseline_jobs, change_counts, steps and warnings. Every comparison row is returned, with no output truncation. Empty inventories produce steps: [] and change_counts: {}. Counts count rows, so an ambiguous identity can contribute separate target and baseline rows. Each row includes identity fields, optional match_method/unmatched_reason, and available target/baseline attempts with IDs, names, states, soft_failed, exit status/signal reason, retry counts, URLs and valid timing metrics. If a job URL is missing but its step ID is available, its URL opens that step in the corresponding build instead. Existing job URLs are preserved.

Outcome precedence and display order are:

Change Meaning
newly_failing Passed → failed/timed_out/expired, with unchanged soft-failure flag
recovered Failed/timed_out/expired → passed, with unchanged soft-failure flag
still_failing Both states are failed/timed_out/expired, with unchanged soft-failure flag
state_changed Soft-failure flag changed (takes precedence over the above), or another state change
retries_changed Same outcome/state but a different final-attempt retry count
added / removed Unique identity present only in target / baseline
unmatched Missing or ambiguous identity; no pair inferred
unchanged Same outcome/state and retry count; timings may still differ

One-sided and unmatched rows take precedence over outcome classification. Two soft-failed attempts can be still_failing; cancellations, broken, waiting_failed and skipped states are not classified as failures. Within each category, execution deltas sort descending (missing treated as zero), with API order retained for ties. execution_seconds is finished minus started; scheduling_seconds is started minus scheduled. Uploaded jobs can inherit the build's creation time as their scheduled time, so this interval can include earlier upstream waiting; it is not isolated scheduler/agent delay. Missing or reversed timestamps omit the metric; zero is valid. execution_delta_seconds is target minus baseline when both are valid, so positive means slower. These are final-attempt durations, not all-retry costs or build wall-clock deltas.

Still-failing jobs can have different causes. Comparison does not prove causality or retry safety. No logs are fetched; follow job URLs/IDs for evidence and use bk job --help for drill-down commands. Text output uses untruncated per-step blocks; JSON/YAML is preferable for filtering and automated analysis.

List a build's annotations

bk build annotations 123 --pipeline my-pipeline --json
bk build annotations acme/my-pipeline/123 --text
bk build annotations https://buildkite.com/acme/my-pipeline/builds/123 --yaml
bk build annotations acme/my-pipeline/123 --json | jq '.annotations[].body_html'

The build argument is required. A plain build number uses --pipeline (a slug or org/pipeline) or the pipeline inferred from configuration or the repository. JSON/YAML never prompts for pipeline selection; specify --pipeline when inference is ambiguous. Qualified selectors use the target organization's credentials. Requires the read_builds token scope.

All pages of both build- and job-scoped annotations are fetched. JSON/YAML includes organization, pipeline, build_number, and annotations (an empty array when there are none). Annotation fields use REST names, including id, context, style, scope, job_id when present, priority, body_html, created_at, and updated_at. Bodies are rendered HTML, not original Markdown; text output also preserves the HTML. --output/-o json|yaml|text and the standard --json, --yaml, --text flags are supported. Text output reports No annotations found. for an empty build. Errors, including a failed later page, return a nonzero exit status with no partial results on stdout.

List a build's failed tests

bk build failed-tests 123 --pipeline my-pipeline --json
bk build failed-tests acme/my-pipeline/123 --text
bk build failed-tests https://buildkite.com/acme/my-pipeline/builds/123 --yaml

The build argument is required. A plain build number uses --pipeline or the pipeline inferred from configuration or the repository. JSON/YAML never prompts for pipeline selection; specify --pipeline when inference is ambiguous. The command fetches all pages of tests with at least one failed execution, including tests that passed on retry, from every Test Engine suite accessible to your token. It does not restrict results to flaky labels or require the build to be failed.

JSON/YAML output includes organization, pipeline, build_number, build_id, failed_tests (an empty array when there are no matches), and note. Each test includes its identity, source location, links, current labels, and execution metrics. Individual execution failure messages and backtraces are not included. Requires read_builds and read_suites token scopes and uploaded Test Engine data associated with the build. The API aggregation window is bounded by retention and ends at most 24 hours after build creation. An empty result does not prove that no tests failed, particularly when data is missing or inaccessible.

Current test labels do not establish that flakiness caused a failure.

Retry a build's failed jobs

bk build retry-failed-jobs 123 --pipeline my-pipeline
bk build retry-failed-jobs acme/my-pipeline/123 --states failed,soft_failed --yes --json
bk build retry-failed-jobs https://buildkite.com/acme/my-pipeline/builds/123 --yes --no-input --text

The required build argument accepts the same number, qualified selector, and URL forms as failed-tests. JSON/YAML never prompts for pipeline selection; specify --pipeline when inference is ambiguous. Confirmation is required unless --yes is supplied. --no-input without --yes fails without retrying. Confirmation prompts go to stderr, leaving structured stdout parseable. Malformed selectors and conflicting explicit build/pipeline targets are rejected.

This calls the public build-wide REST retry_failed_jobs operation and requires write_builds. It is not limited to infrastructure failures and does not check whether jobs are safe to rerun. Retries can repeat side effects, including deployments. By default (including --states ''), all retryable failed jobs are retried. --states accepts a comma-separated list of canceled, expired, failed, soft_failed, and timed_out.

The API returns 202 Accepted, asynchronously queuing retries in the existing build. JSON/YAML includes organization, pipeline, build_number, web_url, retried_jobs_count, and note. The count is jobs queued for retry, including zero when none were queued, not completed or successful retries. The command does not wait; use bk build view or bk build watch to follow the build. If the request was accepted but its count could not be read, the command fails without a receipt and warns you to check the build before resubmitting.

List a build's dynamic step uploads

bk build step-uploads 123 --pipeline my-pipeline --json
bk build step-uploads acme/my-pipeline/123 --text
bk build step-uploads https://buildkite.com/acme/my-pipeline/builds/123 --yaml

The build argument is required and uses the same selectors and prompt-safe pipeline inference as failed-tests. Requires the read_builds token scope. All cursor pages are fetched, newest first. JSON output includes organization, pipeline, build_number, step_uploads (an empty array when none exist), and note. Uploads include their UUID, state (pending, processing, applied, rejected, or failed), source job, replace flag, timestamps, created job count, and any rejection type and outcome message. An unavailable created_jobs_count is omitted in JSON and shown as - in text; an applied upload that created no jobs has a count of 0.

Rejection/failure messages are available without fetching individual uploads. They exclude uploaded configuration and internal failure details; inspect the source job's pipeline upload command output for further diagnostics. Uploaded definitions are not fetched. The API only retains uploads within the build's maximum lifetime, up to 30 days after creation. Older builds return a 410 Gone error, not an empty successful result. Other API errors also fail the command without emitting partial results.

Inspect flaky test failures

List failed tests that Test Engine currently labels flaky for a failed job:

bk job flaky-tests <job-uuid> --text
bk job flaky-tests <job-uuid> --json

The command fetches all pages and includes the job UUID, build UUID, test details, and execution metrics in structured output (JSON or YAML). It requires read_builds and read_suites token scopes and Test Engine executions tagged with the matching build.job_id. Missing or overridden collector job metadata can prevent matches.

Current flaky labels do not prove that flakiness caused the job failure. An empty result does not mean “not flaky”: Test Engine data may be missing or incomplete.

Find failures across triggered builds

bk build view acme/my-pipeline/123 --recursive --json

--recursive returns current hard-failed (failed) and timed-out (timed_out) command jobs from the selected build and its descendants. It follows every available child reference, including passed, asynchronous, and soft-failed triggers, without filtering by build state. Superseded retry attempts and the children of superseded trigger attempts are excluded. Soft-failed command jobs, canceled/expired jobs, configuration-excluded (broken) jobs, and jobs stopped by failed dependencies are not reported as command failures. Running jobs that have promised failure are not terminal failures and are also excluded.

JSON/YAML output contains root_build, complete, failures, and issues. Each failure includes its qualified build (org/pipeline/number), job id, name, state, web_url, log_url, raw_log_url, and trigger_path. The path lists ancestor trigger jobs as org/pipeline/number#job-id, from the root; root-build failures have an empty path. --text provides the same information in a readable form.

This is one CLI invocation, not one API request: the CLI fetches each reachable build once using the existing REST API. It does not download logs, artifacts, or annotations. Build access requires read_builds and permission to read each pipeline; following log pointers separately requires read_build_logs.

Inaccessible children, invalid child references, missing/not-yet-created child builds (except skipped or configuration-excluded triggers), and request failures produce partial output with complete: false, explanatory issues, and a nonzero exit status. Failures from accessible siblings are still returned. complete describes traversal of the observed current attempts, not an atomic snapshot or a guarantee that running builds have finished. Builds created by scripts/API calls without a trigger-job link cannot be discovered this way. --recursive cannot be combined with --summary, --web, or --job-states.

Migrate GitHub Actions secrets

Use bk secret migrate github-actions to move repository Actions secrets into Buildkite without exposing their values to the local machine. See the GitHub Actions secrets migration guide.

Feedback

We'd love to hear any feedback and questions you might have. Please file an issue on GitHub and let us know!

Development

This repository uses mise to pin Go and the main local development tools.

git clone git@github.com:buildkite/cli.git
cd cli/
mise install
mise run build
mise run install
mise run install:global
mise run hooks
mise run format
mise run lint
mise run test
mise run generate
go run main.go --help

mise.toml pins the shared toolchain, including the release helpers used in CI. The module itself remains compatible with Go 1.26.0 as declared in go.mod.

About

A command line interface for Buildkite.

Resources

Contributing

Stars

206 stars

Watchers

20 watching

Forks

Releases

Packages

Used by

Contributors

Languages