Why use the Aspect CLI?
bazel is a build system, not a developer-workflow tool. Every Bazel monorepo eventually grows the same pile of bespoke shell scripts and CI YAML for the things teams actually do every day — format pre-submits, lint enforcement, BUILD-file generation, release-tag delivery, CI-platform reporting. Each is written in a different language, breaks differently, and behaves differently in CI versus on a laptop. There is no shared abstraction, no caching across teams, and no single place to look when something breaks.
The Aspect CLI (aspect) replaces that pile with a single, free, open-source task runner you program in Starlark. Built-in tasks like aspect build, aspect test, aspect format, aspect lint, aspect gazelle, and aspect delivery ship with the patterns every Bazel monorepo eventually re-invents — hold-the-line lint that only fails on violations you introduced, change-detection-driven delivery, structured artifact upload, native integration with GitHub Status Checks, Buildkite Annotations, and the equivalent on GitLab and CircleCI. When the built-ins aren’t enough you write custom tasks in Starlark — the same language you already use for .bzl files — and they run identically on every laptop and every CI provider.
Why it’s worth a 10-minute try
- Zero adoption cost. Apache-licensed, open source, falls through to raw
bazelfor any subcommand the CLI doesn’t wrap (bazel query,bazel info, etc.). You’re not switching build systems — you’re extending the build system you already have with a programmable task-runner layer. - One language for everything.
.aspect/config.axlconfigures the CLI in the same Starlark dialect you write.bzlfiles in. No new YAML schema, no new template language. - Same command in every environment.
aspect lintdoes the same thing on a laptop as in a CI pipeline. Eliminates an entire class of “works on my machine but not in CI” bugs. - No vendor lock-in. Stop using it tomorrow and your Bazel build still works. Aspect Workflows is a separate, optional product.
Built-in tasks
aspect ships with a set of built-in tasks that work out of the box on any CI provider and on any developer machine:
| Task | What it does |
|---|---|
aspect build | Build Bazel targets with retry on transient errors and BES streaming |
aspect test | Run Bazel tests with coverage and test-log upload |
aspect run | Build and run a binary target |
aspect format | Format only the files changed in the PR |
aspect buildifier | Format Starlark files (opt-in via format.alias()) |
aspect lint | Run linters with hold-the-line strategy |
aspect gazelle | Generate and sync BUILD files |
aspect delivery | Deliver only targets whose outputs actually changed |
aspect help to list the tasks available in your repo (built-ins plus any custom ones you’ve added).
What you’ll see
bazel’s output is a wall of INFO: lines. aspect wraps each task in a phased UI, names what each phase is doing, and prints a friendly summary at the end with a per-phase timing breakdown.
The simplest example — aspect test on a cached run:
aspect format shows more phases — it builds the formatter, detects which files changed in the diff against origin/main, formats just those, and reports whether anything was modified:
aspect buildifier (or aspect format) actually reformats files, it tells you the exact command to re-run locally to apply the same fix — and the raw bazel run invocation it shells out to under the hood, so you’re never locked out of reproducing what just happened:
aspect lint is the most interesting one because of hold-the-line: the linter can surface findings in files the PR didn’t touch, but the task still passes — only new violations introduced by the PR’s diff fail the build. In the run below ShellCheck found three issues in examples/lint/hello.sh, but since that file isn’t in the changed-file set (only README.md is), the task ends with No findings:
How tasks are configured
Each task is implemented in AXL, a Starlark dialect. Built-in tasks are zero-config —aspect build, aspect test, and aspect run work the moment you install the CLI. When you need to tune behaviour or register a new task, all knobs live in .aspect/config.axl at your repo root.
A minimal example — set --config=ci on every Bazel invocation when running in CI, and upload failing test logs:
.aspect/config.axl
aspect-build/bazel-examples. The example below sets --config=ci on CI, registers aspect buildifier via format.alias(), declares the lint aspects so CI invocations don’t need to repeat --aspect, points aspect delivery at OCI-push rules, and enables artifact uploads for failed tests, the Bazel profile, and the BEP:
.aspect/config.axl
Custom tasks
When the built-ins don’t cover what you need, write your own. Drop a.axl file into .aspect/, define a task, and it’s automatically discoverable via aspect <name>:
.aspect/codegen.axl
Aspect Extension Language
AXL — the Aspect Extension Language — is a Starlark dialect for configuring the CLI, writing custom tasks, and extending Bazel with new BUILD-file generators (see Gazelle extensions below) and Build Event Protocol subscribers. Starlark was chosen because Bazel users already know it from.bzl files — the mental model (functions, load statements, deterministic evaluation) transfers directly. AXL draws on Buck’s BXL and Tilt, which use Starlark for the same purpose.
The AXL reference documents every type and built-in. For day-to-day work, the Guides section covers what most extensions need.
Watch Alex’s BazelCon 2025 talk for an overview of the extension language and the design decisions behind it:
Gazelle extensions
The same Starlark engine powers a separate extension surface: BUILD-file generation. You can teach Gazelle about a new language or project layout in Starlark instead of Go — see How to extend Gazelle BUILD file generation and the Aspect 150 training course.Running in CI
The sameaspect <task> command you run locally works identically in CI. Running tasks in CI has ready-to-paste YAML for GitHub Actions, Buildkite, GitLab CI, and CircleCI.
Aspect Workflows self-hosted runners take this further: tasks detect the runner environment automatically and pick up remote cache, remote execution, and pre-warmed NVMe output bases. No extra configuration — the same commands that run locally just go faster.
Live examples
Theaspect-build/bazel-examples repo runs aspect <task> pipelines on every commit across all four supported CI providers. Click through to inspect a current build, plus per-task Buildkite annotations, GitHub Status Checks, and a sample PR task summary comment: Live examples.
