Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.aspect.build/llms.txt

Use this file to discover all available pages before exploring further.

Why run aspect in CI?

CI for a Bazel monorepo grows the same scaffolding everywhere: install bazel and pin its version, wire up the remote cache, route failed test logs into artifact storage, post a per-job status check, comment on the PR with lint findings, retry the occasional transient error, time the build phases for telemetry, gate delivery so you don’t push identical binaries on every commit. Each piece is small but the pile is fragile, copy-pasted across teams, and breaks differently on every CI provider. The Aspect CLI replaces that scaffolding. Every aspect <task> invocation is self-contained: it handles flag configuration, artifact upload, status checks, lint comments, retry, and selective delivery internally — and it does all of it the same way on GitHub Actions, Buildkite, GitLab CI, and CircleCI.

What you get out of the box

When a CI step is just aspect <task>, you get all of the following with no extra YAML or shell glue:
  • Retry on transient Bazel errors. BLAZE_INTERNAL_ERROR, LOCAL_ENVIRONMENTAL_ERROR, and similar transient codes trigger a bounded automatic retry — instead of the copy-pasted || retry loop that wakes someone up at 3 a.m. when it gets the wrong errors.
  • Native CI status checks. Each task posts a per-step status (named by --task-key) to GitHub Status Checks, Buildkite Annotations, GitLab job annotations, and the equivalent on CircleCI — so reviewers see the result of the lint job, the test job, and the format job individually, not a single opaque “CI passed” line.
  • Live result streaming. Lint findings stream to PR review comments as the job runs, with one-click suggested fixes when the linter offers them. Reviewers see the first failure within seconds of it happening, not after the entire pipeline finishes.
  • Smart changed-file detection. aspect format and aspect lint know which files changed in the PR — they diff against the merge base by default and fall back to the GitHub PR Files API when git diff can’t answer (shallow clones, fetch-depth-restricted runners) — so they only act on what the PR touched, with no hand-rolled “diff against origin/main” script.
  • Hold-the-line lint. Pre-existing violations don’t fail the build; only new ones added by the PR do. You enable the strictest lint rules without forcing a flag-day refactor.
  • Artifact upload to your CI’s native storage. Test logs, the Bazel execution log, build profile (chrome trace), and the BEP are uploaded via the CI provider’s native artifact API — GitHub Actions artifacts, Buildkite artifacts, GitLab job artifacts, CircleCI store_artifacts. Drop the bespoke “upload on failure” step.
  • Selective delivery. aspect delivery re-deploys only the services whose Bazel-built outputs actually changed since the last release — driven by the build graph, not git diffs or timestamps.
  • Same command, same contract, every provider. aspect lint on a laptop does the same thing as aspect lint on GitHub Actions. Cuts an entire class of “works on my machine but not in CI” bugs.
On Aspect Workflows self-hosted runners, aspect <task> also detects the runner environment and applies the remote cache, RBE, and BES flags automatically — no .bazelrc plumbing.

Standard tasks

The built-in tasks, each invoked the same way in every CI step:
CommandWhat it doesFull reference
aspect build --task-key build -- //...Build Bazel targetsaspect build / aspect test
aspect test --task-key test -- //...Run Bazel testsaspect build / aspect test
aspect format --task-key formatFormat source files (changed files by default)aspect format
aspect lint --task-key lint -- //...Run linters with hold-the-line strategyaspect lint
aspect gazelle --task-key gazelleGenerate and sync BUILD filesaspect gazelle
aspect buildifier --task-key buildifierFormat Starlark files (opt-in via format.alias())aspect buildifier
aspect delivery --task-key deliveryDeliver only targets whose outputs actually changedaspect delivery
Custom tasks defined in your repo’s .aspect/*.axl files invoke the same way: aspect <name> --task-key <name>.

Task key

--task-key assigns a short identifier to the CI step. The key appears in GitHub Status Checks, Buildkite Annotations, and similar CI-platform integrations, so pick a name that’s meaningful in a status list. Plain keys like build or test are the right default. Only suffix with a CI name (e.g. build-gha) if you run the same task on multiple CI providers simultaneously and need distinct status check names per provider.

Authentication

ASPECT_API_TOKEN is optional, but recommended. Without it, aspect tasks still build, test, format, and lint normally. With it, you unlock the CI-platform integrations that make the tasks worth running in CI in the first place: status checks, inline PR comments, suggested fixes, and the more accurate changed-file detection above. Store the token as a CI secret on each provider you use, then pass it to aspect:
  • GitHub Actions — pass it via with: aspect-api-token: on the aspect-build/setup-aspect action. setup-aspect exchanges it for a short-lived JWT and persists only the JWT — the long-lived secret never lands in GITHUB_ENV and isn’t visible to other steps in the job.
  • Buildkite, GitLab CI, CircleCI — expose it as the ASPECT_API_TOKEN env var on the job.
See Authenticating the Aspect CLI for the one-time account, GitHub App, and token-generation setup.

Complete pipeline examples

Two sets of CI pipeline examples — one for provider-hosted runners (GitHub Actions’ ubuntu-latest, Buildkite’s hosted agents, GitLab.com runners, CircleCI cloud runners, your own self-hosted VMs) and one for Aspect Workflows self-hosted runners (which ship with aspect pre-installed and route Bazel through the deployment’s caching infrastructure automatically). On GitHub Actions, the same aspect-build/setup-aspect action covers both cases — it installs the launcher on provider-hosted runners, no-ops on Workflows CI runners, and exchanges ASPECT_API_TOKEN for a session JWT either way. On Buildkite, GitLab CI, and CircleCI there’s no equivalent action yet; ephemeral examples install the launcher inline with curl -fsSL https://install.aspect.build | bash, while Workflows-CI-runner examples skip that step.

On provider-hosted runners

Cloud VMs and containers don’t ship with aspect or bazel, so each example installs them at the start of every job. The launcher then reads .aspect/version.axl to pin the CLI version, so local and CI stay in sync.
.github/workflows/aspect.yaml
name: CI

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  id-token: write

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect build --task-key build -- //...

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect test --task-key test -- //...

  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect format --task-key format

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect lint --task-key lint -- //...

  delivery:
    needs: [test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect delivery --task-key delivery
setup-aspect also installs Bazelisk and wires --disk_cache / --repository_cache to the GHA cache. Pin to a full-length SHA with the version annotated in a trailing comment per GitHub’s third-party action security guidance; find the latest SHA on the setup-aspect releases page.

On Aspect Workflows CI runners

Aspect Workflows CI runners ship with aspect and bazel pre-installed and warm. The runs-on: / agents: / tags: / resource_class: value targets your Workflows CI runner queue (aspect-default here is the example queue name from your Terraform runner group).
.github/workflows/aspect-workflows.yaml
name: Aspect Workflows

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

permissions:
  id-token: write

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect build --task-key build -- //...

  test:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect test --task-key test -- //...

  format:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect format --task-key format

  lint:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect lint --task-key lint -- //...

  delivery:
    needs: [test]
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    steps:
      - uses: actions/checkout@v6
      - uses: aspect-build/setup-aspect@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect delivery --task-key delivery
setup-aspect skips the launcher install on Workflows CI runners (the runner image already ships aspect) and runs rosetta bazelrc > /etc/bazel.bazelrc so raw bazel calls in subsequent steps also pick up the runner’s cache, BES backend, and NVMe disk cache.

Live examples

The aspect-build/bazel-examples repo runs these pipelines on all four supported CI providers — on GitHub Actions it runs both the provider-hosted-runner and Workflows-CI-runner versions side by side; Buildkite, GitLab CI, and CircleCI each run the Workflows-CI-runner version. Click through to inspect a real, current build. Source lives in two places: GitHub (used by the GitHub Actions, Buildkite, and CircleCI pipelines) and GitLab (used by the GitLab CI pipeline).
CI providerLive pipeline
GitHub ActionsActions tab
BuildkiteRecent builds
GitLab CI/CDPipelines
CircleCIPipeline runs

What aspect <task> reports back

aspect <task> posts task results to three surfaces — examples below are from real runs of aspect-build/aspect-cli’s own CI:
  • PR task summary comment — a single comment Marvin posts to the PR thread summarising every task in the pipeline. See example →
  • GitHub Status Checks — one check per aspect <task> invocation, named by --task-key, surfaced on the PR’s Checks tab and on the commit itself.
  • Buildkite annotations (when running on Buildkite) — one annotation per aspect <task> invocation, rendered at the top of the build page.
Per-task links from a recent run: The task name links to its GitHub Status Check; the Buildkite column links to the matching annotation.