> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Running tasks in CI

> Run Aspect CLI tasks in CI with ready-to-paste YAML for GitHub Actions, Buildkite, GitLab CI, and CircleCI, covering retries and status checks.

## 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:name`) 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](/docs/aspect-workflows/overview) 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:

| Command                                    | What it does                                        | Full reference                                           |
| ------------------------------------------ | --------------------------------------------------- | -------------------------------------------------------- |
| `aspect build --task:name build -- //...`  | Build Bazel targets                                 | [aspect build / aspect test](/docs/cli/tasks/build_test) |
| `aspect test --task:name test -- //...`    | Run Bazel tests                                     | [aspect build / aspect test](/docs/cli/tasks/build_test) |
| `aspect format --task:name format`         | Format source files (changed files by default)      | [aspect format](/docs/cli/tasks/format)                  |
| `aspect lint --task:name lint -- //...`    | Run linters with hold-the-line strategy             | [aspect lint](/docs/cli/tasks/lint)                      |
| `aspect gazelle --task:name gazelle`       | Generate and sync BUILD files                       | [aspect gazelle](/docs/cli/tasks/gazelle)                |
| `aspect buildifier --task:name buildifier` | Format Starlark files (opt-in via `format.alias()`) | [aspect buildifier](/docs/cli/tasks/buildifier)          |
| `aspect delivery --task:name delivery`     | Deliver only targets whose outputs actually changed | [aspect delivery](/docs/cli/tasks/delivery)              |

Custom tasks defined in your repo's `.aspect/*.axl` files invoke the same way: `aspect <name> --task:name <name>`.

## Task name

`--task:name` assigns a short name uniquely identifying the task invocation. The name appears in GitHub Status Checks, Buildkite Annotations, and similar CI-platform integrations, so pick something meaningful in a status list. Plain names matching the task kind like `build` or `test` are the right default. 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.

If you don't pass `--task:name`, it's auto-generated:

* **On CI**, from the CI job name — `<kind>-<job>` (e.g. `test-ci-linux`), so status checks read meaningfully and stay stable across runs. The job name comes from the host: GitHub Actions `GITHUB_JOB`, Buildkite `BUILDKITE_STEP_KEY` (else `BUILDKITE_LABEL`), CircleCI `CIRCLE_JOB`, GitLab `CI_JOB_NAME`. Steps using native parallelism append the shard index (Buildkite `BUILDKITE_PARALLEL_JOB`, CircleCI `CIRCLE_NODE_INDEX`) → `<kind>-<job>-<shard>`. If the same name is still generated again on the same run and machine (e.g. one job script invoking the same task kind twice), a `-2`, `-3`, … suffix is appended.
* **Off CI**, from a random friendly suffix — `<kind>-<suffix>` (e.g. `test-fluffy-parakeet`).

Setting `--task:name` explicitly is still recommended in CI for the clearest, most intentional status check names.

<Note>
  **GitHub Actions matrix jobs:** unlike Buildkite/CircleCI parallelism, GitHub exposes no per-shard environment variable (matrix values live only in `${{ matrix.* }}`, and `GITHUB_JOB` is the same for every shard), so all shards of a matrix auto-generate the same name and collapse into one PR-summary row. For per-shard status, set an explicit `--task:name=<kind>-${{ matrix.os }}`.
</Note>

<Note>
  The `--task-key` flag is a deprecated alias for `--task:name`. It still works but prints a warning and will be removed in a future release. Switch to `--task:name`.
</Note>

## 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`](https://github.com/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** — store it as a [Buildkite secret](https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets) named exactly `ASPECT_API_TOKEN`. The CLI reads it directly from the secret store, so no `env:` mapping is needed and the raw token never enters the job environment.
* **GitLab CI, CircleCI** — expose it as the `ASPECT_API_TOKEN` env var on the job (a masked CI/CD variable / project environment variable).

See [Authenticating the Aspect CLI](/docs/cli/authentication) 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**](/docs/aspect-workflows/overview) 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`](https://github.com/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.

<Tabs>
  <Tab title="GitHub Actions">
    ```yaml title=.github/workflows/aspect.yaml theme={null}
    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@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
            with:
              aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
          - run: aspect build --task:name build -- //...

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

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

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

      delivery:
        needs: [test]
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v6
          - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
            with:
              aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
          - run: aspect delivery --task:name 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](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions); find the latest SHA on the [setup-aspect releases page](https://github.com/aspect-build/setup-aspect/releases).
  </Tab>

  <Tab title="Buildkite">
    ```yaml title=.buildkite/pipeline.yaml theme={null}
    steps:
      - key: build
        label: ":bazel: Build"
        command: |
          curl -fsSL https://install.aspect.build | bash
          aspect build --task:name build -- //...

      - key: test
        label: ":bazel: Test"
        command: |
          curl -fsSL https://install.aspect.build | bash
          aspect test --task:name test -- //...

      - key: format
        label: ":hammer_and_wrench: Format"
        command: |
          curl -fsSL https://install.aspect.build | bash
          aspect format --task:name format

      - key: lint
        label: ":lint-roller: Lint"
        command: |
          curl -fsSL https://install.aspect.build | bash
          aspect lint --task:name lint -- //...

      - key: delivery
        label: ":package: Delivery"
        depends_on: [test]
        command: |
          curl -fsSL https://install.aspect.build | bash
          aspect delivery --task:name delivery
    ```

    For the platform integrations, store the token as a [Buildkite secret](https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets) named exactly `ASPECT_API_TOKEN` — the CLI reads it directly from the secret store, so no `env:` mapping is needed.

    To avoid repeating the install in every step, factor the curl line into a [Buildkite agent hook](https://buildkite.com/docs/agent/v3/hooks#environment-hook) once and drop it from each `command:`.
  </Tab>

  <Tab title="GitLab CI">
    ```yaml title=.gitlab-ci.yml theme={null}
    variables:
      ASPECT_API_TOKEN: $ASPECT_API_TOKEN  # set as a masked CI/CD variable

    default:
      before_script:
        - curl -fsSL https://install.aspect.build | bash
        - export PATH="$HOME/.local/bin:$PATH"

    workflow:
      rules:
        - if: '$CI_PIPELINE_SOURCE != "schedule"'

    stages:
      - CI

    build:
      stage: CI
      script:
        - aspect build --task:name build -- //...

    test:
      stage: CI
      script:
        - aspect test --task:name test -- //...

    format:
      stage: CI
      script:
        - aspect format --task:name format

    lint:
      stage: CI
      script:
        - aspect lint --task:name lint -- //...

    delivery:
      stage: CI
      needs: [test]
      script:
        - aspect delivery --task:name delivery
    ```

    The `default.before_script` block runs the launcher install once per job, so individual jobs stay clean.
  </Tab>

  <Tab title="CircleCI">
    ```yaml title=.circleci/config.yml theme={null}
    version: 2.1

    parameters:
      force_targets:
        type: string
        default: ""

    commands:
      install-aspect:
        description: Install the Aspect CLI launcher
        steps:
          - run:
              name: Install Aspect CLI
              command: curl -fsSL https://install.aspect.build | bash

    workflows:
      ci:
        jobs:
          - build
          - test
          - format
          - lint
          - delivery:
              requires: [test]
        when:
          not:
            equal:
              - scheduled_pipeline
              - << pipeline.trigger_source >>

    jobs:
      build:
        docker:
          - image: cimg/base:current
        steps:
          - checkout
          - install-aspect
          - run: aspect build --task:name build -- //...

      test:
        docker:
          - image: cimg/base:current
        steps:
          - checkout
          - install-aspect
          - run: aspect test --task:name test -- //...

      format:
        docker:
          - image: cimg/base:current
        steps:
          - checkout
          - install-aspect
          - run: aspect format --task:name format

      lint:
        docker:
          - image: cimg/base:current
        steps:
          - checkout
          - install-aspect
          - run: aspect lint --task:name lint -- //...

      delivery:
        docker:
          - image: cimg/base:current
        environment:
          ASPECT_WORKFLOWS_DELIVERY_FORCE_TARGETS: << pipeline.parameters.force_targets >>
        steps:
          - checkout
          - install-aspect
          - run: aspect delivery --task:name delivery
    ```

    The reusable `install-aspect` command keeps each job to a single install step.
  </Tab>
</Tabs>

### 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).

<Tabs>
  <Tab title="GitHub Actions">
    ```yaml title=.github/workflows/aspect-workflows.yaml theme={null}
    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@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
            with:
              aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
          - run: aspect build --task:name build -- //...

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

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

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

      delivery:
        needs: [test]
        runs-on: [self-hosted, aspect-workflows, aspect-default]
        steps:
          - uses: actions/checkout@v6
          - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
            with:
              aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
          - run: aspect delivery --task:name delivery
    ```

    setup-aspect skips the launcher install on Workflows CI runners (the runner image already ships `aspect`) and runs `aspect ci bazelrc` to generate `~/.bazelrc` so vanilla `bazel` calls in subsequent steps also pick up the runner's cache, BES backend, and NVMe disk cache.
  </Tab>

  <Tab title="Buildkite">
    ```yaml title=.buildkite/pipeline.yaml theme={null}
    steps:
      - key: build
        label: ":bazel: Build"
        plugins:
          - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
        command: aspect build --task:name build -- //...
        agents:
          queue: aspect-default

      - key: test
        label: ":bazel: Test"
        plugins:
          - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
        command: aspect test --task:name test -- //...
        agents:
          queue: aspect-default

      - key: format
        label: ":hammer_and_wrench: Format"
        plugins:
          - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
        command: aspect format --task:name format
        agents:
          queue: aspect-default

      - key: lint
        label: ":lint-roller: Lint"
        plugins:
          - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
        command: aspect lint --task:name lint -- //...
        agents:
          queue: aspect-default

      - key: delivery
        label: ":package: Delivery"
        depends_on: [test]
        plugins:
          - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
        command: aspect delivery --task:name delivery
        agents:
          queue: aspect-default
    ```

    The `queue: aspect-default` value must match the `queue` field in your Workflows Terraform runner group.
  </Tab>

  <Tab title="GitLab CI">
    ```yaml title=.gitlab-ci.yml theme={null}
    include:
      - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.26.8

    variables:
      ASPECT_API_TOKEN: $ASPECT_API_TOKEN  # set as a masked CI/CD variable

    workflow:
      rules:
        - if: '$CI_PIPELINE_SOURCE != "schedule"'

    stages:
      - CI

    build:
      extends: .setup-aspect
      stage: CI
      tags: [aspect-workflows, aspect-default]
      script:
        - aspect build --task:name build -- //...

    test:
      extends: .setup-aspect
      stage: CI
      tags: [aspect-workflows, aspect-default]
      script:
        - aspect test --task:name test -- //...

    format:
      extends: .setup-aspect
      stage: CI
      tags: [aspect-workflows, aspect-default]
      script:
        - aspect format --task:name format

    lint:
      extends: .setup-aspect
      stage: CI
      tags: [aspect-workflows, aspect-default]
      script:
        - aspect lint --task:name lint -- //...

    delivery:
      extends: .setup-aspect
      stage: CI
      needs: [test]
      tags: [aspect-workflows, aspect-default]
      script:
        - aspect delivery --task:name delivery
    ```

    The `aspect-default` tag must match the `queue` field in your Workflows Terraform runner group.
  </Tab>

  <Tab title="CircleCI">
    ```yaml title=.circleci/config.yml theme={null}
    version: 2.1

    orbs:
      setup-aspect: aspect-build/setup-aspect@2026.26.10

    parameters:
      force_targets:
        type: string
        default: ""

    workflows:
      aspect-workflows:
        jobs:
          - build
          - test
          - format
          - lint
          - delivery:
              requires: [test]
        when:
          not:
            equal:
              - scheduled_pipeline
              - << pipeline.trigger_source >>

    jobs:
      build:
        machine: true
        resource_class: YOUR-ORG/aspect-default
        working_directory: /mnt/ephemeral/workdir
        steps:
          - checkout
          - setup-aspect/setup
          - run:
              name: Build
              command: aspect build --task:name build -- //...

      test:
        machine: true
        resource_class: YOUR-ORG/aspect-default
        working_directory: /mnt/ephemeral/workdir
        steps:
          - checkout
          - setup-aspect/setup
          - run:
              name: Test
              command: aspect test --task:name test -- //...

      format:
        machine: true
        resource_class: YOUR-ORG/aspect-default
        working_directory: /mnt/ephemeral/workdir
        steps:
          - checkout
          - setup-aspect/setup
          - run:
              name: Format
              command: aspect format --task:name format

      lint:
        machine: true
        resource_class: YOUR-ORG/aspect-default
        working_directory: /mnt/ephemeral/workdir
        steps:
          - checkout
          - setup-aspect/setup
          - run:
              name: Lint
              command: aspect lint --task:name lint -- //...

      delivery:
        machine: true
        resource_class: YOUR-ORG/aspect-default
        working_directory: /mnt/ephemeral/workdir
        environment:
          ASPECT_WORKFLOWS_DELIVERY_FORCE_TARGETS: << pipeline.parameters.force_targets >>
        steps:
          - checkout
          - setup-aspect/setup
          - run:
              name: Delivery
              command: aspect delivery --task:name delivery
    ```

    Replace `YOUR-ORG/aspect-default` with the resource class you created in CircleCI.
  </Tab>
</Tabs>

## 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](https://github.com/aspect-build/bazel-examples) (used by the GitHub Actions, Buildkite, and CircleCI pipelines) and [GitLab](https://gitlab.com/aspect-build/bazel-examples) (used by the GitLab CI pipeline).

| CI provider    | Live pipeline                                                                                      |
| -------------- | -------------------------------------------------------------------------------------------------- |
| GitHub Actions | [Actions tab](https://github.com/aspect-build/bazel-examples/actions?query=branch%3Amain)          |
| Buildkite      | [Recent builds](https://buildkite.com/aspect-build/bazel-examples/builds?branch=main)              |
| GitLab CI/CD   | [Pipelines](https://gitlab.com/aspect-build/bazel-examples/-/pipelines?scope=all\&ref=main)        |
| CircleCI       | [Pipeline runs](https://app.circleci.com/pipelines/github/aspect-build/bazel-examples?branch=main) |

### What `aspect <task>` reports back

`aspect <task>` posts task results to three surfaces — examples below are from real runs of [`aspect-build/aspect-cli`](https://github.com/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 →](https://github.com/aspect-build/aspect-cli/pull/1170#issuecomment-4587638034)
* **GitHub Status Checks** — one check per `aspect <task>` invocation, named by `--task:name`, 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.

| Task (GitHub Status Check)                                                         | Buildkite annotation                                                                                                                        |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [`aspect build`](https://github.com/aspect-build/aspect-cli/runs/78802755984)      | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1004-4e74-83f8-7df31a6288c8\&tab=annotations) |
| [`aspect test`](https://github.com/aspect-build/aspect-cli/runs/78802839780)       | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1005-4cff-8aa1-7e7552f1cfef\&tab=annotations) |
| [`aspect format`](https://github.com/aspect-build/aspect-cli/runs/78802676524)     | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1001-4c69-a76a-3b0c3c448eca\&tab=annotations) |
| [`aspect buildifier`](https://github.com/aspect-build/aspect-cli/runs/78802668723) | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1000-403f-9bd2-19e655539c29\&tab=annotations) |
| [`aspect lint`](https://github.com/aspect-build/aspect-cli/runs/78802736796)       | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1007-42ca-89c2-a5cb87806df7\&tab=annotations) |
| [`aspect gazelle`](https://github.com/aspect-build/aspect-cli/runs/78802664004)    | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-1002-487d-99bc-f5db899afb17\&tab=annotations) |
| [`aspect delivery`](https://github.com/aspect-build/aspect-cli/runs/78802669497)   | [build #3019 →](https://buildkite.com/aspect-build/aspect-cli/builds/3019/canvas?sid=019e81ff-100b-4e13-876d-836b8329f115\&tab=annotations) |
