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

# How to pin the Aspect CLI version

> Pin the Aspect CLI version per repository with .aspect/version.axl, configure custom download sources, and decouple updates from CI runner upgrades.

The `aspect` binary you [install](/docs/cli/install) is a thin **launcher** — it provisions and execs the real `aspect-cli` for your project. The launcher decides which CLI version to run by reading a single file in your repository:

```
<repo-root>/.aspect/version.axl
```

Pinning a version here means every developer and every CI runner in the project executes the same `aspect-cli`, regardless of how recently they installed the launcher.

<Tip>
  Because AXL-powered tasks (`aspect build`, `aspect test`, `aspect lint`, `aspect delivery`) ship with the CLI, bumping the version in `.aspect/version.axl` pulls in newer task behavior **independent of any Aspect Workflows CI runner upgrade**.
</Tip>

## Pin a version

Create `.aspect/version.axl` at your repository root, alongside `MODULE.bazel` (or `MODULE.aspect`):

```python theme={null}
version("2026.27.7")
```

The launcher downloads `v2026.27.7` directly from the [Aspect CLI GitHub Releases](https://github.com/aspect-build/aspect-cli/releases) the first time it's needed and caches the binary locally. If the download fails, the launcher reports an error — **a pinned version never silently falls back to a different release**.

## No project `version.axl`

If the launcher can't find a `.aspect/version.axl` in any parent directory of the working directory (i.e. there is no project root pinning a version), it resolves the version in this order:

1. **User-level pin** — `~/.aspect/version.axl`. As of Aspect CLI **2026.27.7**, the launcher falls back to a `version.axl` in your home directory. This file uses the same syntax as a project-level pin and lets you set a default CLI version for work outside any pinned project, without touching a repository.
2. **Floating mode** — if no user-level file exists either, the launcher queries the GitHub releases API for the most recent non-prerelease and downloads that. The resolved tag is cached for 24 hours so subsequent runs do not hit the API.

<Note>
  A project-level <code>.aspect/version.axl</code> always wins. The <code>\~/.aspect/version.axl</code> fallback only applies when no project root is found, so it never overrides a version a repository has pinned.
</Note>

Floating mode is convenient for one-off use, but every contributor and CI runner can end up on a different `aspect-cli` version. Pin a version — per project, or per user via `~/.aspect/version.axl` — for any project where reproducibility matters.

## Custom sources

The optional `sources` list overrides where the launcher looks for the CLI binary. Sources are tried in order; the first one that succeeds wins.

```python theme={null}
version(
    "2026.27.7",
    sources = [
        local("bazel-bin/cli/aspect"),
        github(
            org = "aspect-build",
            repo = "aspect-cli",
        ),
    ],
)
```

When `sources` is omitted, it defaults to `[github(org = "aspect-build", repo = "aspect-cli")]`.

### `local(path)`

Use a binary at a path relative to the repository root. Useful when developing the CLI locally:

```python theme={null}
local("bazel-bin/cli/aspect")
```

The file is copied into the launcher cache on each invocation so that build-system clean operations don't break a running CLI.

### `github(org, repo, tag?, artifact?)`

Download from a GitHub release:

```python theme={null}
github(
    org = "aspect-build",
    repo = "aspect-cli",
)
```

When `tag` and `artifact` are omitted the launcher derives defaults:

* `tag` defaults to `v{version}` (e.g. `v2026.27.7`)
* `artifact` defaults to `{repo}-{target}` (e.g. `aspect-cli-aarch64-apple-darwin`)

Override either with explicit strings if you publish releases under a different convention:

```python theme={null}
github(
    org = "my-org",
    repo = "aspect-cli-fork",
    tag = "release-{version}",
    artifact = "aspect-cli-{os}-{arch}",
)
```

### `http(url, headers?)`

Download from an arbitrary URL — for example, an internal mirror:

```python theme={null}
http(
    url = "https://cdn.example.com/aspect-cli/{version}/aspect-cli-{target}",
)
```

Pass custom headers for authenticated endpoints:

```python theme={null}
http(
    url = "https://internal.example.com/aspect-cli/{version}/aspect-cli-{target}",
    headers = {
        "Authorization": "Bearer <token>",
    },
)
```

## Template variables

`tag`, `artifact`, and `url` strings support `{variable}` placeholders the launcher replaces at runtime:

| Variable    | Example                                             | Description                           |
| ----------- | --------------------------------------------------- | ------------------------------------- |
| `{version}` | `2026.27.7`                                         | The version from the `version()` call |
| `{os}`      | `darwin`, `linux`                                   | Operating-system kernel name          |
| `{arch}`    | `aarch64`, `x86_64`                                 | CPU architecture (Bazel naming)       |
| `{target}`  | `aarch64-apple-darwin`, `x86_64-unknown-linux-musl` | LLVM target triple                    |

These are the only supported placeholders. `version.axl` is parsed as Starlark syntax but not evaluated — only string literals and function-call structure are extracted, so expressions, variables, and conditionals are not available.

## Caching

Downloaded binaries are cached under the system cache directory:

* macOS: `~/Library/Caches/aspect/launcher/`
* Linux: `~/.cache/aspect/launcher/`

The cache path is derived from a SHA-256 hash of the tool name and source URL, so different versions coexist without conflict. Override the location with the `ASPECT_CLI_DOWNLOADER_CACHE` environment variable.

Set `ASPECT_DEBUG=1` to log the download and cache resolution flow if you need to debug what the launcher is doing.

## CI considerations

CI runners share the same launcher behavior as developer machines, so a committed `.aspect/version.axl` pins the CLI version everywhere at once.

* **Aspect Workflows CI runners** ship with the launcher pre-installed; pinning a version updates both developer machines and runners with a single PR.
* **GitHub Actions / other CI** — see [How to install the Aspect CLI](/docs/cli/install#install-with-github-actions) for installing the launcher in your CI image. Once installed, the launcher reads the same `.aspect/version.axl` from the checkout.
