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

# aspect gazelle

> Migrate Rosetta gazelle and configure tasks to aspect gazelle, configuring BUILD file synchronization and check mode in Aspect CLI config.axl.

The legacy Rosetta `gazelle` task ran a [bazel-gazelle](https://github.com/bazel-contrib/bazel-gazelle) `.check` target against the repo and annotated the build with the produced diff if BUILD files were out of date. `aspect gazelle` replaces it (and also subsumes the older `configure` task — see below).

`aspect gazelle` builds your gazelle target and runs it once with `-mode=diff` to capture the unified diff that gazelle would produce. From there, two modes:

* **Default (apply mode):** the captured diff is applied to your working tree via `git apply -p0`. Best for local development.
* **`--check` (check mode):** the diff is reported without modifying the tree. Recommended for CI.

In either mode, a non-empty diff signals "BUILD files out of date" and the task exits non-zero.

## Two legacy tasks, one new task

Rosetta had two different YAML tasks that both produced "BUILD files are out of date" verdicts:

* **`gazelle`** — ran a user-defined `gazelle.check` target (default `//:gazelle.check`) and looked at its exit code + stderr. Most users with bespoke gazelle setups landed here.
* **`configure`** — ran the legacy aspect-cli's built-in `bazel configure` command, which embedded a prebuilt gazelle binary inside the CLI itself. No `//tools/gazelle` target was required because the binary shipped with the CLI. This task wasn't in wide use; it was the path for repos that didn't want to maintain their own gazelle target.

**Both migrate to `aspect gazelle`.** Today the new task needs a `//tools/gazelle:gazelle` target wired to [`aspect_gazelle_prebuilt`](https://github.com/aspect-build/aspect-gazelle) — see [Setup](#setup). For `configure` users, the key thing is that `aspect_gazelle_prebuilt` fetches a prebuilt gazelle binary, so you keep `configure`'s "no Go toolchain, no build-from-source" benefit; the only new step is declaring that one target once in `tools/gazelle/BUILD`.

<Tip>
  **Coming soon:** in a near-term release the `aspect gazelle` task will be able to fetch and run a prebuilt gazelle binary on its own — no `//tools/gazelle` target required. That brings it back to the same shape as the legacy `configure` task: a single `aspect gazelle` call works against a vanilla repo with no Bazel-side wiring. For now, both paths through this doc (current `gazelle` migration and `configure` migration) need a target — that requirement will go away.
</Tip>

## Setup

Add `aspect_gazelle_prebuilt` to `MODULE.bazel` and define a gazelle target. We recommend the prebuilt binary (no Go toolchain required); see the [aspect-gazelle release notes](https://github.com/aspect-build/aspect-gazelle/releases) for the latest version.

```python theme={null}
# MODULE.bazel
bazel_dep(name = "aspect_gazelle_prebuilt", version = "0.0.8")
```

```python theme={null}
# tools/gazelle/BUILD
load("@aspect_gazelle_prebuilt//:def.bzl", "aspect_gazelle")

aspect_gazelle(
    name = "gazelle",
    languages = ["go", "proto", "python"],  # whichever fit your repo
)
```

If you'd rather build gazelle from source (upstream `bazel-contrib/bazel-gazelle` macro + a Go toolchain), `aspect gazelle` works against that too. The current implementation's runtime `-mode=diff` override is portable across both prebuilt and from-source wrappers.

## What changed

| Area                                                   | YAML-configured tasks                                                                                                                     | Aspect CLI tasks                                                                                                                                                                                    |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invocation                                             | `rosetta run gazelle` · `rosetta run configure` (which called `bazel configure` internally)                                               | `aspect gazelle`                                                                                                                                                                                    |
| Gazelle binary                                         | `gazelle`: user-supplied target (default `//:gazelle.check`) · `configure`: built into the legacy aspect-cli                              | User-supplied target (default `//tools/gazelle:gazelle`); future: bundled, like `configure`                                                                                                         |
| Target                                                 | `gazelle`: `target:` (default `//:gazelle.check`) · `configure`: n/a — used the bundled binary                                            | `--gazelle-target` (default `//tools/gazelle:gazelle`)                                                                                                                                              |
| Mode                                                   | Both always `-mode diff` (`gazelle.check` ran gazelle that way; `bazel configure` passed `--mode=diff`)                                   | Default applies via `git apply -p0`; `--check` keeps tree clean (recommended in CI)                                                                                                                 |
| Gazelle subcommand                                     | Always the default (no explicit subcommand)                                                                                               | `--gazelle-command=fix\|update` (default: none — gazelle's own default applies)                                                                                                                     |
| Extra gazelle flags                                    | None — no passthrough                                                                                                                     | `--gazelle-flag=<flag>` (repeatable) — forwarded to the gazelle binary                                                                                                                              |
| Directory scope                                        | Whole repo                                                                                                                                | Positional args after flags: `aspect gazelle tools/go services/api` — gazelle only walks those subtrees                                                                                             |
| Detection                                              | `gazelle`: exit code from the gazelle binary; non-zero meant BUILDs out of date · `configure`: special `CONFIGURE_DIFF` exit code         | Captured `-mode=diff` stdout — non-empty diff = BUILDs out of date                                                                                                                                  |
| Apply path                                             | Both out-of-band: user runs `bazel run //:gazelle` (or `bazel configure` for the `configure` task) after the failed CI step, then commits | In-band by default: `git apply -p0 <captured-diff>` updates the working tree as part of the `aspect gazelle` invocation. `--check` mirrors the legacy diff-only behavior (recommended in CI)        |
| Gazelle invocations per task run                       | One (the gazelle binary itself)                                                                                                           | One — the apply step uses `git apply`, not a second gazelle invocation, which avoids re-running gazelle's slow whole-repo scan                                                                      |
| Diff archival                                          | Both: annotated with the (truncated) task output                                                                                          | `--upload-gazelle-diff` (off by default) uploads the patch as a CI artifact via the same uploader build/test/format use; download URL is recorded on `ArtifactsTrait.artifact_urls["gazelle_diff"]` |
| [Workspaces](/docs/cli/migration#top-level-workspaces) | `workspaces:` top-level list ran each task once per directory                                                                             | `cd <dir>` in your CI config; run `aspect gazelle` from there. To cover multiple workspaces, declare a separate CI step per directory.                                                              |

## Configuration

Many of the settings below can be applied in either of two ways:

* **CLI flag** — pass the flag at invocation time (e.g. `aspect gazelle --check`). Good for overrides on individual task invocations, or experimenting with a setting before committing it.
* **`.aspect/config.axl`** — declare it once in your repo's AXL config so every `aspect gazelle` invocation picks it up. Good for settings that should apply to all invocations of a task type.

Pick whichever fits — you only need one. Where both work, the sections below show them side-by-side under **After**.

### Gazelle target

**Before** — `.aspect/workflows/config.yaml`:

```yaml theme={null}
tasks:
  gazelle:
    target: //:gazelle.check
```

**After** — CLI flag or `config.axl`:

<CodeGroup>
  ```shell CLI theme={null}
  aspect gazelle --gazelle-target=//tools/gazelle:gazelle
  ```

  ```python .aspect/config.axl theme={null}
  def config(ctx: ConfigContext):
      ctx.tasks["gazelle"].args.gazelle_target = "//tools/gazelle:gazelle"
  ```
</CodeGroup>

<Note>
  The default gazelle target moved from `//:gazelle.check` to `//tools/gazelle:gazelle`. Two reasons for the change:

  * **Out of the root package.** Defining `gazelle()` at `//tools/gazelle` instead of `//:gazelle.check` keeps the gazelle `load(...)` out of your root `BUILD.bazel`. Loads in the root package fire on every invocation that touches it (any wildcard, query, or rule that resolves a root-package label), so keeping gazelle-only machinery out of that path saves fetch and analysis overhead on commands that have nothing to do with gazelle.
  * **No more `.check` suffix.** The task drives gazelle in `-mode=diff` at runtime via a flag override, so a single target works for both apply and check semantics.

  If you relied on the legacy default and your `gazelle.check` target lives at the root, point `--gazelle-target` at the apply target (`//:gazelle`, not `//:gazelle.check`). Or migrate the BUILD file to `tools/gazelle/BUILD` to match the new default — see the [Setup](#setup) section.
</Note>

### Migrating from the `configure` task

If your repo previously had:

```yaml theme={null}
tasks:
  configure:
```

and relied on the legacy aspect-cli's built-in gazelle — the migration is to add an [`aspect_gazelle`](https://github.com/aspect-build/aspect-gazelle) target backed by `aspect_gazelle_prebuilt` at `//tools/gazelle:gazelle` (see [Setup](#setup)) and switch to `aspect gazelle`. `aspect_gazelle_prebuilt` fetches a prebuilt gazelle binary, so you keep `configure`'s "no Go toolchain, no build-from-source" benefit. Once that's wired up, `aspect gazelle --check` is the direct replacement for `rosetta run configure`.

```yaml theme={null}
# Before
tasks:
  configure:

# After (CI YAML — see Examples below for the full per-host shape):
- aspect gazelle --check
```

### Mode: apply vs. check

**Before** — both legacy tasks always ran in diff-only mode. The user fixed any reported issues by re-running `bazel run :gazelle` (for the `gazelle` task) or `bazel configure` (for `configure`).

**After** — default behavior applies the diff for you (handy locally), `--check` matches the legacy diff-only behavior (recommended in CI):

```shell theme={null}
aspect gazelle             # apply changes via `git apply -p0`, exit 1 if anything changed
aspect gazelle --check     # don't modify the tree; exit 1 if anything would change
```

Why the default switched: locally, `aspect gazelle` and "actually update my BUILDs" are usually the same intent. Saving the user from a second invocation matters more than tree-cleanliness on a developer workstation. CI inverts both incentives — `--check` is the right default there.

In default (apply) mode the task captures the patch from `gazelle -mode=diff`, then runs `git apply -p0 <patch>` rather than re-invoking gazelle a second time. On large monorepos that skips the second whole-repo scan, which is gazelle's slow phase.

<Note>
  **Forgetting `--check` on CI is not a footgun.** The exit code is identical with or without it: any non-empty diff exits 1. Default mode just additionally applies the patch via `git apply -p0` to the runner's checkout (which CI throws away anyway), so you pay a few milliseconds of `git apply` and get slightly stranger terminal output ("Gazelle updated N files" instead of "would update") before the same trailing `ERROR: BUILD files are out of date` and red CI step. `--check` is recommended on CI because it's faster and cleaner, not because forgetting it is unsafe.
</Note>

### Gazelle subcommand (`fix` vs `update`)

The legacy tasks always ran gazelle without an explicit subcommand (defaulting to `update` / `fix` depending on the language plugin). `aspect gazelle` exposes this as `--gazelle-command`:

```shell theme={null}
aspect gazelle                          # no subcommand (gazelle's default)
aspect gazelle --gazelle-command=update # explicitly run 'update'
aspect gazelle --gazelle-command=fix    # 'fix': also removes obsolete rules
```

Most repos can leave this unset. Use `--gazelle-command=fix` when you want gazelle to clean up deleted files or apply language-specific migrations on top of normal rule generation.

`update-repos` is intentionally not supported here — its interaction with `-mode=diff` is untested. Run it directly with `bazel run //tools/gazelle:gazelle -- update-repos` when you need to update Go repository rules.

### Passing arbitrary gazelle flags (`--gazelle-flag`)

`--gazelle-flag` passes extra flags directly to the gazelle binary (repeatable):

<CodeGroup>
  ```shell CLI theme={null}
  aspect gazelle --gazelle-flag=-progress --gazelle-flag=-r tools/go
  ```

  ```python .aspect/config.axl theme={null}
  def config(ctx: ConfigContext):
      ctx.tasks["gazelle"].args.gazelle_flags = ["-progress", "-r"]
  ```
</CodeGroup>

This is useful for:

* **`-progress`** — print progress output (aspect-gazelle prebuilt only).
* **`-cache`** — enable gazelle's build cache (aspect-gazelle prebuilt only).
* **`-r`** — recurse into subdirectories.
* **Any other upstream gazelle flag** not yet exposed as a first-class task arg.

Note: flags specific to `aspect_gazelle_prebuilt` (like `-progress` and `-cache`) will cause errors if you're using a from-source gazelle target that doesn't recognize them. Don't set those in `config.axl` if your target may vary.

Any `-mode`/`--mode` flag passed via `--gazelle-flag` is ignored — the task controls mode internally, and changes are applied via `git apply` rather than a second gazelle invocation, so there's no gazelle call that a user-supplied `-mode` would reach.

### Limiting scope with directories

Pass directories as positional arguments after all flags. Gazelle limits its walk to those subtrees:

```shell theme={null}
# Walk only tools/go and services/api
aspect gazelle tools/go services/api

# CI check limited to a subtree
aspect gazelle --check services/payments
```

In `.aspect/config.axl`, set `dirs` as a list:

```python theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["gazelle"].args.dirs = ["tools/go", "services/api"]
```

When no directories are specified (the default), gazelle walks the entire repo from the workspace root — matching the legacy behavior.

### Diff archival

**Before** — the legacy tasks attached the diff to a CI annotation (truncated to \~20 lines, with the rest in the terminal-detail block).

**After** — `--upload-gazelle-diff` (default `false`):

<CodeGroup>
  ```shell CLI theme={null}
  aspect gazelle --check --upload-gazelle-diff
  ```

  ```python .aspect/config.axl theme={null}
  def config(ctx: ConfigContext):
      ctx.tasks["gazelle"].args.upload_gazelle_diff = True
  ```
</CodeGroup>

When BUILD files are out of date, the task writes the diff to a job-scoped tmpfile and uploads it as `gazelle.patch` via the same uploader [build/test use for testlogs](/docs/cli/migration/build_test). Works on GitHub Actions, Buildkite, CircleCI, and GitLab. The download URL (where the CI provider returns one) lands on `ArtifactsTrait.artifact_urls["gazelle_diff"]` so future status-check features can link to it.

No-op when the working tree is clean (no diff to upload), or when not running on a recognized CI host.

## Examples

### Standard gazelle check on PRs

**Before** — `.aspect/workflows/config.yaml`:

```yaml theme={null}
tasks:
  gazelle:
    target: //:gazelle.check
```

**After** — CI configuration:

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  on:
    pull_request:
      branches: [main]

  jobs:
    gazelle:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write   # ArtifactUpload uses the runner's OIDC token to call the GitHub Actions artifact API
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - name: Gazelle
          run: aspect gazelle --check --task:name gazelle
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":deer: Gazelle"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect gazelle --check --task:name gazelle
      agents:
        queue: aspect-default
  ```

  ```yaml GitLab theme={null}
  include:
    - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.26.8

  gazelle:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script:
      - aspect gazelle --check --task:name gazelle
  # Set ASPECT_API_TOKEN as a masked CI/CD variable in Settings → CI/CD → Variables.
  ```

  ```yaml CircleCI theme={null}
  version: 2.1

  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.26.10

  jobs:
    gazelle:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            name: Gazelle
            command: aspect gazelle --check --task:name gazelle

  workflows:
    ci:
      jobs:
        - gazelle
  # Set ASPECT_API_TOKEN as a project environment variable or context secret.
  ```
</CodeGroup>

### Archive the diff as a CI artifact (`--upload-gazelle-diff`)

`--upload-gazelle-diff` writes gazelle's patch to a tmpfile and uploads it as `gazelle.patch` via the same uploader build/test use, so reviewers can apply it locally without re-running gazelle:

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  jobs:
    gazelle:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@2306377a61c45954ab2df7c7311698b109364352 # v2026.26.9
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - name: Gazelle
          run: aspect gazelle --check --task:name gazelle --upload-gazelle-diff
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":deer: Gazelle"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect gazelle --check --task:name gazelle --upload-gazelle-diff
      agents:
        queue: aspect-default
  ```

  ```yaml GitLab theme={null}
  include:
    - component: $CI_SERVER_FQDN/aspect-build/setup-aspect-gitlab-component/setup@2026.26.8

  gazelle:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    script:
      - aspect gazelle --check --task:name gazelle --upload-gazelle-diff
  # Set ASPECT_API_TOKEN as a masked CI/CD variable in Settings → CI/CD → Variables.
  ```

  ```yaml CircleCI theme={null}
  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.26.10

  jobs:
    gazelle:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            command: aspect gazelle --check --task:name gazelle --upload-gazelle-diff
  # Set ASPECT_API_TOKEN as a project environment variable or context secret.
  ```
</CodeGroup>

The download URL (when the CI provider returns one — GitHub, GitLab, CircleCI) is recorded on `ArtifactsTrait.artifact_urls["gazelle_diff"]` for downstream status-check or annotation features to surface.
