aspect build and aspect test are the core CI tasks. They wrap bazel build and bazel test with environment configuration, live build progress in your CI platform, artifact upload, and a reproducer command for each failure.
The command you run in CI works the same on a developer’s machine. If a build breaks, running aspect build --task:name build //... locally reproduces it.
What they do
aspect build builds Bazel targets. It:
- Streams the Build Event Protocol (BEP) so features can observe each target’s outcome, timing and outputs.
- Connects to a remote cache and build event service: from the runner’s environment on an Aspect Workflows CI runner, or from your default deployment with
--remoteelsewhere. - Retries on
BLAZE_INTERNAL_ERROR(exit code 37) andLOCAL_ENVIRONMENTAL_ERROR(36), transient failures that don’t reflect your code.
aspect test does the same with bazel test, and adds:
- Code coverage with LCOV output and an optional external tool.
- Test log upload through
ArtifactUpload, so failing test output is one click away in CI.
Environment configuration
On Aspect Workflows runners, theWorkflows feature configures Bazel for the runner; see Aspect Workflows runners. To turn on remote execution there, set ctx.features[Workflows].args.remote_exec = True in config.axl.
On any other CI runner, the Deployment feature connects the task to your default deployment, because --remote defaults to auto: on for CI when the job can authenticate, off elsewhere. See Aspect Cloud CI setup, and the built-in features reference for every feature’s options.
Options
Both tasks take these flags.aspect test adds the coverage flags.
Configuration
Set options in.aspect/config.axl for every invocation, or with flags for one.
Targets
Pass target patterns as positional arguments:config.axl:
Bazel flags
Type Bazel flags directly. Each one the task doesn’t define is forwarded to Bazel: after the task name as a command option, before it as a startup option. See flag passthrough.config.axl, with conditional logic:
Test filters and coverage
The merged report is read from
$(bazel info output_path)/_coverage/_coverage_report.dat. The coverage flags have no effect without --coverage.
For publishing to Codecov and per-provider CI examples, see Collect and publish code coverage.
GitHub status checks
TheGithubStatusChecks feature posts a check run to the commit as soon as aspect build starts. It:
- Creates the check run immediately, so the build shows as in progress.
- Streams live progress: target counts and current failures.
- Completes with pass or fail and a build summary with artifact download links.
aspect-api-token on GitHub Actions, a Buildkite secret (secrets: [ASPECT_API_TOKEN]), ASPECT_API_TOKEN in the environment on GitLab and CircleCI. It also needs the Aspect Workflows GitHub App installed.
Check names come from --task:name. Set a stable, distinct name on each invocation:
GitLab merge request status
These features post results to GitLab from merge request pipelines:GitlabCommitStatuses: a commit status per task on the SHA under review, moving throughpending,running, andsuccess,failed,skippedorcanceledas the task progresses.
A task reports
aborted (🔥, exit 8) when Bazel is interrupted — Ctrl-C, or a CI runner cancelling the job. Running out of memory (exit 33) and losing the remote environment (exit 32) still report as failed. On GitLab aborted maps to canceled, not failed, so pipeline automation matching on failed will not match a cancelled job.GitlabStatusComments: one merge request note with every task’s live status, updated as jobs finish.
CI_MERGE_REQUEST_IID is set).
Status names come from --task:name, as on GitHub. For linking the App and token roles, see GitLab App.
Artifact upload
ArtifactUpload uploads build outputs to your CI platform’s artifact storage. Every kind is opt-in, because artifacts are visible to anyone with repo access.
.aspect/config.axl
With
upload_build_logs = "failed", the stdout and stderr of every failed build action (compile, codegen, genrule and so on) are uploaded. Each log starts with a provenance header (# target, # action, # primary output, # stream) and lands under a path mirroring the target, such as foo/bar/baz/Genrule.stderr.log for //foo/bar:baz. Same-mnemonic actions under one target are told apart by the primary output’s basename. GitHub and GitLab status checks then show a Build logs link next to Test logs. It applies wherever Bazel runs: build, test, run, warming, gazelle, format, lint and delivery.
ArtifactUpload uses the runner’s own CI credentials (permissions: id-token: write on GitHub Actions, the agent token on Buildkite, CI_JOB_TOKEN on GitLab). It doesn’t need ASPECT_API_TOKEN.
CI example
.github/workflows/ci.yaml

