GitHub Action that installs the Aspect CLI launcher, installs Bazelisk (skipped automatically if bazel is already on PATH), points Bazel at Aspect Cloud's remote cache and BES, and authenticates with the Aspect API — all in one step.
Both the remote cache and the web UI that the BES stream powers are available on Aspect Cloud's Free Tier. See aspect.build/docs for more info.
Minimal — latest launcher, Bazelisk, and the Aspect remote cache:
- uses: actions/checkout@v6
- uses: aspect-build/setup-aspect@<commit-sha>
with:
aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
- run: bazel build //...
- run: bazel test //...That is the whole setup. setup-aspect runs aspect setup bazelrc, which writes ~/.aspect/bazelrc with your Aspect deployment's remote cache and BES and try-imports it from ~/.bazelrc — so a plain bazel call shares a cache with every other job and branch, and streams its build to Aspect.
The rc goes to ~/.bazelrc, never into the checkout, so the repository stays clean.
Or run Aspect tasks — aspect build, aspect test, aspect lint and the rest
— which run the same builds with Aspect's own reporting and reach the
deployment on CI without a flag of their own:
- uses: actions/checkout@v6
- uses: aspect-build/setup-aspect@<commit-sha>
with:
aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
- run: aspect build //...
- run: aspect test //...A task configures its own Bazel invocation, so it needs neither the generated
rc nor --remote; what it does need is aspect on PATH, which this action
installs. See Aspect CLI tasks.
Full — pin versions, key the repository cache per workflow, and authenticate:
permissions:
id-token: write # required for Aspect CLI's ArtifactUpload feature
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: aspect-build/setup-aspect@<commit-sha>
with:
launcher-version: 2026.38.30
bazelisk-version: 1.x
bazelisk-cache: true
repository-cache: ${{ github.workflow }}
aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
- run: bazel build //...
- run: bazel test //...repository-cache holds the bytes Bazel downloads for external repositories,
in the GitHub Actions cache rather than Aspect's. Keying it on
github.workflow gives each workflow its own entry, which is worth doing where
workflows pull genuinely different dependency sets and would otherwise churn
one shared entry; a single true shares one across them all. The build outputs
themselves need no input here — those go to the Aspect remote cache, shared
across every job and branch.
Pin to a full-length commit SHA, not a branch or tag — tags are mutable and can be repointed at malicious code, so SHA-pinning is the GitHub-recommended way to use third-party actions. Annotate with the version in a trailing comment for readability, and let Dependabot or Renovate keep the SHA fresh:
- uses: aspect-build/setup-aspect@<commit-sha> # v2026.38.0Find the SHA on the Releases page — each release's notes include a copy-paste snippet pinned to its SHA. (We also push lightweight YYYY.VV weekly tags for discoverability.) Either way, pin to the SHA rather than the tag, since tags are mutable.
setup-aspect runs in one of two modes depending on the runner:
- Install the Aspect CLI launcher (
aspectonPATH). Reads.aspect/version.axlfrom your repo on first invocation to fetch the matching CLI binary. PointsASPECT_LAUNCHER_CACHEandASPECT_CLI_CACHEat GHA-cacheable directories (with distinct roots) and restores them, so the download is skipped on warm runs. - Install Bazelisk (default:
latest). Skipped ifbazelis already on PATH (you don't need bothsetup-bazelandsetup-aspect). Caches the binary viaactions/tool-cacheand viaactions/cache(unlessbazelisk-cache: false). - Restore caches via
@actions/cache. The repository, Bazelisk, and Aspect CLI caches are on by default on ephemeral runners; the disk cache is opt-in (seedisk-cache). The post-job hook saves them on exit. - Authenticate to the Aspect API via
aspect auth login --with-api-tokenifaspect-api-tokenis set. The resulting short-lived JWT is persisted locally; the long-lived<client_id>:<secret>is never written toGITHUB_ENV(see Security below). - Point
~/.bazelrcat the Aspect remote cache by runningaspect setup bazelrc, which on CI writes~/.aspect/bazelrcand adds atry-importfor it at the top of~/.bazelrc, so vanillabazelcalls use the deployment's remote cache and BES. No flags are passed: the CLI detects CI and picks that layout over the<workspace>/.aspect/bazelrcpair meant to be committed. Setbazelrc-generate: falseto write neither file, orbazelrc-hometo write the checkout's pair instead. - Append
~/.bazelrcdirectives —--repository_cache,--disk_cachewhen enabled, and any extra lines from thebazelrcinput. Idempotent, and appended below step 5'stry-import, so these lines override the generated ones.
Step 4 comes first deliberately: the rc task enables a deployment's endpoints only where something can authenticate them, so the credential has to be in hand before it runs. Step 6's placement is about precedence rather than survival — the rc task only adds its try-import at the top of ~/.bazelrc and leaves the rest alone, and Bazel takes the last value of a flag, so lines below that import win over the generated rc.
The runner image already provides aspect and bazel, and aspect <task> invocations always wire themselves into the runner's caching infrastructure on their own. setup-aspect's job on these runners is to extend the same wiring to vanilla bazel calls outside of aspect <task> — many pipelines mix aspect build with a separate bazel build step, and without setup-aspect those bare bazel invocations would miss the deployment's remote cache, BES backend, and local NVMe disk cache.
- Waits for runner cache warming to complete if it is still in progress — the same wait
aspect <task>performs before running, extended to jobs that go on to run vanillabazelso they don't race the warming bootstrap and miss the warmed caches. - Configures vanilla
bazelto use the runner's caching infrastructure — the same remote cache, repository cache, and NVMe disk cache thataspect <task>already uses. The rc is generated byaspect setup bazelrc(writes~/.bazelrc), with a legacy fallback for runners whose CLI predates that task. If neither is available the action warns but does not fail — only vanillabazelcalls go unconfigured. - Authenticates to the Aspect API (same as ephemeral mode).
- Skips launcher install, Bazelisk install, GHA cache wiring, and
~/.bazelrcappend — not needed on Workflows runners (aspect/bazelare already available and Bazel is routed through the runner's own caching).
Detection is based on the ASPECT_WORKFLOWS_RUNNER env var.
| Input | Default | Purpose |
|---|---|---|
launcher-version |
latest |
Aspect CLI launcher version to install (e.g. 2026.38.30). The CLI version is pinned by .aspect/version.axl in your repo — the launcher reads that file and downloads the matching CLI on first aspect invocation. Ignored on Workflows runners or when launcher-install is false. |
launcher-install |
true |
Whether to install the Aspect CLI launcher. Set to false if you're providing aspect yourself (e.g. via a `curl install.aspect.build |
aspect-api-token |
— | Long-lived <CLIENT_ID>:<SECRET> token, typically passed via the GitHub Actions secrets context (e.g. as secrets.ASPECT_API_TOKEN in a with: block). When set, setup-aspect runs aspect auth login --with-api-token (piping the token via stdin) — the short-lived JWT it produces is persisted locally for downstream aspect <task> calls via ctx.aspect.auth.credentials(). The long-lived token is not exported to GITHUB_ENV. Leave empty to skip the auth step. |
bazelisk-version |
latest |
Bazelisk version to install (semver range or exact, e.g. 1.x or 1.21.0). Default: latest (downloaded via GitHub's /releases/latest/download/<asset> redirect — no API call, no rate-limit risk). The install is skipped regardless of this input if bazel is already on PATH (setup-bazel ran first, you're on an Aspect Workflows runner, etc.). |
bazelisk-cache |
true |
Cache the Bazelisk binary across runs (keyed on .bazelversion). On by default; set false to disable. |
bazelrc-generate |
true |
Whether to write ~/.aspect/bazelrc, and the try-import for it in ~/.bazelrc, via aspect setup bazelrc. On by default — that file is what makes a plain bazel build //... share a cache across jobs and stream to Aspect. The deployment defaults to Aspect Cloud; aspect-api-token is what lets Bazel authenticate to it. false leaves ~/.bazelrc alone, on a Workflows runner too; the installs and the login still happen, and aspect <task> steps are unaffected either way. |
disk-cache |
false |
Cache Bazel's --disk_cache outputs across runs, through the GHA cache. Off by default — the remote cache covers the same ground and is shared across jobs and branches, where this is per-runner and capped by the GHA cache's 10 GB per-repo budget. true re-enables it; a string segregates caches by stage (a common pattern is to key on the GHA github.workflow value). Worth turning on when bazelrc-generate is off. Ignored on Workflows runners. |
repository-cache |
true |
Enable Bazel --repository_cache (external-repo download bytes). On by default. Set to a string to segregate caches by stage (e.g. the GHA github.workflow value). false disables. Ignored on Workflows runners. |
bazelrc-remote |
"" | Which of the deployment's endpoints the generated rc turns on, passed as --remote=<value>. Unset leaves the CLI's auto (cache + BES on CI, nothing off it). Same grammar as aspect build --remote: exec adds remote execution, no-cache / no-bes / no-exec subtract, none enables nothing. |
bazelrc-home |
"" | Which rc to write, passed as --home=<value>. Unset leaves the CLI's auto (~/.aspect/bazelrc on CI, the checkout's off it). false writes the committed <workspace>/.aspect/bazelrc instead, for a job whose purpose is regenerating it. |
bazelrc-force |
false |
Regenerate the rc even if one is already there (--force). Worth turning on for a persistent self-hosted runner, whose home directory survives between jobs; an ephemeral runner starts clean, so it changes nothing there. |
bazelrc |
"" | Extra lines appended to ~/.bazelrc. Multiline YAML supported. Append-only and idempotent. Appended after the try-import the rc task adds at the top of ~/.bazelrc, so these lines win where they set the same flag. Ignored on Workflows runners. |
Which Bazel flags the generated rc carries is a repository choice, not a workflow
one. To drop a flag the rc would otherwise set, name it in the repo's
.aspect/config.axl, where it covers every CI provider and local runs alike:
def config(ctx: ConfigContext):
ctx.tasks["setup/bazelrc"].args.omit_bazel_flags = [
"--execution_log_compact_file",
]Endpoints, credentials, and the runner's output paths cannot be omitted.
The typical pattern in GHA workflows is:
# DON'T do this — token visible to every step in the job
env:
ASPECT_API_TOKEN: ${{ secrets.ASPECT_API_TOKEN }}That exposes the long-lived <client_id>:<secret> to every step in the job, including any untrusted third-party action you call. If a malicious action reads process.env, the secret leaks.
setup-aspect handles the token differently:
- You pass
aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}as an input — visible only to setup-aspect's step. - setup-aspect calls
aspect auth login --with-api-token, piping the token via stdin. - The CLI exchanges the long-lived token with the Aspect API for a short-lived session JWT, and persists the JWT on disk.
- Downstream
aspect <task>invocations in any later step pick up the persisted JWT viactx.aspect.auth.credentials(). - The long-lived token is never written to
GITHUB_ENV— downstream steps see only the JWT on disk.
The JWT has a bounded TTL (24h by default; see Aspect docs). For jobs longer than the JWT TTL, downstream tasks will need to re-authenticate; in practice that affects vanishingly few CI jobs since GitHub Actions caps individual jobs at 6h on hosted runners.
Several Aspect CLI features (notably ArtifactUpload, which uses the GHA artifact API) require the workflow to grant id-token: write to the job. setup-aspect can't grant the permission itself — only the workflow YAML can — but it detects when the permission is missing and emits a ::warning:: pointing at the docs:
permissions:
id-token: writeIf you don't use any feature that requires OIDC, you can ignore the warning.
You typically don't need both — setup-aspect handles Bazelisk install + cache wiring itself. But if you do run both:
- Cache keys don't collide: setup-aspect uses
setup-aspect-*prefixes; setup-bazel usessetup-bazel-*. - Cache defaults are inverted. setup-bazel ships its disk/repository/Bazelisk caches off by default; setup-aspect ships them on (on ephemeral runners) — the common case wants caching, so you opt out rather than in. Set any of
disk-cache/repository-cache/bazelisk-cachetofalseto disable. - Bazelisk is shared. Both actions use
${RUNNER_TOOL_CACHE}/bazeliskso they reuse the cached binary when at the same version. ~/.bazelrc— setup-bazel overwrites, setup-aspect appends. Run setup-aspect last if you want both actions' directives in the file.bazelalready on PATH — setup-aspect skips its Bazelisk install ifbazelis already there.
Recommendation: pick one. setup-aspect alone handles every concern setup-bazel does, plus the Aspect-specific install + auth steps.
- GitHub Actions runner running Linux or macOS. Windows is not supported (the Aspect CLI launcher is POSIX-only).
- On Aspect Workflows runners only, the action configures the environment such that vanilla
bazelcalls use the Aspect Workflows CI runner NVME drive & deployment remote cache automatically.
- It does not invoke any
aspecttask — you callaspect <task>yourself in subsequent steps. - It does not install raw Bazel. Set
bazelisk-versionto install Bazelisk (recommended), or use bazel-contrib/setup-bazel separately if you need Bazel without Bazelisk.
Inspired by bazel-contrib/setup-bazel by Alex Rodionov. The caching architecture (key shape, @actions/cache integration, post-hook save) and Bazelisk install flow are adapted from setup-bazel with attribution — see THIRD_PARTY_NOTICES.md for the full MIT license text and per-file headers in index.js, post.js, config.js, and install.js.
- Aspect CLI documentation
- Aspect CLI Quickstart — install to a custom task in 10 minutes.
- Running AXL tasks in CI — pipeline examples for GitHub Actions, Buildkite, GitLab CI, CircleCI.
- Aspect Workflows — managed CI runners with
aspectpre-installed plus remote cache and RBE.