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.

aspect build and aspect test are the core CI tasks. They wrap bazel build and bazel test with automatic environment configuration, live build progress in your CI platform, artifact upload, and reproducible local invocations. The same command you run in CI works identically on a developer’s machine — no wrappers, no CI-only flag soup. If a build breaks, running aspect build --task-key build //... locally reproduces it exactly.

What they do

aspect build builds Bazel targets. Under the hood it:
  • Streams all Build Event Protocol (BEP) events so features can observe individual target outcomes, timing, and outputs
  • Injects remote cache / BES flags via the BazelDefaults feature when running on an Aspect Workflows CI runner (or any runner with the relevant env vars)
  • Retries automatically on BLAZE_INTERNAL_ERROR and LOCAL_ENVIRONMENTAL_ERROR — transient failures that don’t reflect your code
aspect test is the same but runs bazel test. It adds:
  • Code coverage support with LCOV output and optional external tool integration
  • Test log upload (via ArtifactUpload) so failing test output is one click away in CI

Automatic environment configuration

On Aspect Workflows runners, BazelDefaults automatically injects the right BES endpoint, remote cache/execution flags, and runner-specific optimizations. You don’t declare them in CI YAML or commit them to .bazelrc — the task reads the runner environment and does the right thing. On standard runners, BazelDefaults is a no-op: the same invocation runs against your local output base with no remote connections. This is the key insight: the same aspect build //... command is fully portable across every CI system and every developer machine.

Configuration

Configure in .aspect/config.axl (applies to every invocation) or via CLI flags (per-invocation overrides).

Targets

Default target pattern is ... (all targets in the workspace). Override with positional args:
aspect build //services/... //libs/...
aspect test //backend/...
Or lock them in config.axl:
def config(ctx: ConfigContext):
    ctx.tasks["build"].args.targets = ["//services/...", "//libs/..."]

Additional Bazel flags

Pass extra Bazel flags without editing .bazelrc:
aspect build --bazel-flag=--config=remote --bazel-flag=--jobs=100 //...
aspect build --bazel-startup-flag=--host_jvm_args=-Xmx4g //...
Or in config.axl with conditional logic:
def config(ctx: ConfigContext):
    is_ci = bool(ctx.std.env.var("CI"))
    if is_ci:
        ctx.tasks["build"].args.bazel_flags = ["--config=remote", "--jobs=100"]

Test filters and coverage

# Run only integration tests (exclude slow ones)
aspect test --bazel-flag=--test_tag_filters=integration,-slow //...

# Collect LCOV coverage and run genhtml
aspect test \
  --coverage \
  --coverage-report=coverage.dat \
  --coverage-tool=genhtml \
  --coverage-tool-arg=--output-directory=coverage-html \
  --coverage-tool-arg={report} \
  //...
Coverage support resolves the LCOV report from Bazel’s $(bazel info output_path)/_coverage/_coverage_report.dat. The {report} and {lcov} placeholders in --coverage-tool-arg are substituted with the actual report path for compatibility with different tools (genhtml, codecov, etc.).

GitHub Status Checks

The GithubStatusChecks feature posts a check run to the commit the moment aspect build starts:
  • Creates the check run immediately (CI platform and developers can see the build is in progress)
  • Streams live progress: target counts, current failures
  • Completes with pass/fail and a build summary with artifact download links
This requires ASPECT_API_TOKEN in the job environment and the Aspect Workflows GitHub App installed. Without those, builds and tests work normally — the check is silently skipped with a single log line explaining what’s missing. Status check names derive from --task-key. Set a stable, distinct key on each invocation:
aspect build --task-key build //...
aspect test --task-key test //...

Artifact upload

ArtifactUpload uploads build outputs to your CI platform’s native storage. All kinds are opt-in (nothing is uploaded by default) because artifacts are accessible to anyone with repo access.
.aspect/config.axl
load("@aspect//feature/artifacts.axl", "ArtifactUpload")

def config(ctx: ConfigContext):
    ctx.features[ArtifactUpload].args.upload_test_logs = "failed"  # only failing tests
    ctx.features[ArtifactUpload].args.upload_profile   = True      # Bazel perf profile
    ctx.features[ArtifactUpload].args.upload_bep       = True      # Build Event Protocol log
ArtifactFlagWhen to use
Test logsupload_test_logs (none/failed/executed/all)Debug flaky tests; start with failed
Bazel profileupload_profilePerformance analysis with bazel analyze-profile
Build Event Protocolupload_bepBES-based tooling; env-var values are redacted before upload
Compact execution logupload_exec_logDeep action investigation; contains raw env vars — keep off on public repos
ArtifactUpload uses the runner’s native credentials (permissions: id-token: write on GitHub Actions, agent token on Buildkite, CI_JOB_TOKEN on GitLab). It does not require ASPECT_API_TOKEN.

How it works under the hood

Both tasks use the same BES event streaming infrastructure:
  1. BES tap — a local Build Event Service listener captures every BEP event in a streaming loop, processing up to 10,000 events per tick with a minimum 500ms heartbeat between ticks.
  2. Feature lifecycle — at task_started, features subscribe to the event stream. GithubStatusChecks creates the check run; ArtifactUpload sets up upload queues; BazelDefaults injects flags.
  3. Retry budget — if Bazel exits with BLAZE_INTERNAL_ERROR or LOCAL_ENVIRONMENTAL_ERROR, the task retries automatically. Default: up to 3 additional attempts. Configurable with --bazel-retry-attempts.
  4. Graceful teardown — on cancellation, the task calls bazel cancel before exiting so orphaned Bazel servers don’t accumulate on CI runners.

CI examples

name: CI
on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  build:
    runs-on: [self-hosted, aspect-workflows, aspect-default]
    permissions:
      id-token: write
    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]
    permissions:
      id-token: write
    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 //...