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

> Migrate the Rosetta format task to aspect format, configuring rules_lint formatter targets and change detection in config.axl for Aspect CLI CI runs.

The legacy Rosetta `format` task ran a [rules\_lint](https://github.com/aspect-build/rules_lint) `format_multirun` target against changed files, archived a diff, and surfaced an annotation if the formatter modified anything. `aspect format` replaces it.

`aspect format` builds your formatter target, determines the changed file set, and runs the formatter against it. Formatters edit files in place, so a non-zero git status after the run signals that formatting was required. Whether to fail the CI step on that condition is up to your CI script.

## What changed

| Area                                                   | YAML-configured tasks                                                                                 | Aspect CLI tasks                                                                                                                                                                                           |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Invocation                                             | `bazel format` (legacy Aspect CLI command)                                                            | `aspect format`                                                                                                                                                                                            |
| Formatter target                                       | `target:` (default `//:format`)                                                                       | `--formatter-target` (default `//tools/format:format`)                                                                                                                                                     |
| Scope                                                  | `format_changed: true` formats changed files only                                                     | `--scope=changed` (default) formats changed files only; pass `--scope=all` for every file the formatter handles                                                                                            |
| Changed-file detection                                 | Local `git diff-tree` / `git diff-index` against the head commit, with a GitHub PR Files API fallback | Local `git diff <merge-base>` by default; GitHub PR Files API as fallback when `git diff` can't resolve the diff base (on a PR + authenticated)                                                            |
| Detection                                              | Checked `git diff` after the formatter to detect "formatting required"                                | `aspect format` snapshots the tree pre-run via `git stash create`, then diffs against that snapshot — non-destructive, robust to pre-existing dirty state                                                  |
| Diff archival                                          | Archived to artifacts, plus a CI annotation pointing to it                                            | `--upload-format-diff` (off by default) uploads the patch as a CI artifact via the same uploader build/test use; download URL is recorded on `ArtifactsTrait.artifact_urls["format_diff"]`                 |
| Soft-fail                                              | `soft_fail: true` downgraded the failure to a warning                                                 | `--soft-fail` (off by default) — same semantics: WARNING + exit 0 instead of failure. The formatter binary's own non-zero exit still fails the task regardless                                             |
| [Workspaces](/docs/cli/migration#top-level-workspaces) | `respect_workspaces: true` filtered changed files to the current workspace                            | `--ignore-pattern='nested/**'` (or in `.aspect/config.axl`) filters nested Bazel workspaces. Combine with `cd <dir>` in CI when you want different formatter targets per workspace                         |
| Ignore patterns                                        | `ignore_patterns: [glob, …]` excluded matching files from the diff outcome                            | `--ignore-pattern` (repeatable) — same effect, plus also drops the matched files from the formatter's input in changed-files mode so it never runs on them. Pattern syntax: `*`, `**`, `?`; case-sensitive |

## 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 format --scope=all //...`). 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 format` 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**.

### Formatter target

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

```yaml theme={null}
tasks:
  format:
    target: //:format
```

**After** — CLI flag or `config.axl` (only needed if your formatter target isn't at the new default `//tools/format:format`):

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

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

<Note>
  The default formatter target moved from `//:format` to `//tools/format:format`. Putting `format_multirun` in a sibling package keeps its `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 format-only machinery out of that path saves fetch and analysis overhead on commands that have nothing to do with formatting.

  If your formatter still lives at the root, set `--formatter-target=//:format` (or pin it in `config.axl`). Repos that already have their formatter at `//tools/format:format` don't need to set the flag at all.
</Note>

### Scope: changed files vs. the whole tree

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

```yaml theme={null}
tasks:
  format:
    format_changed: true   # default
```

**After** — default behavior already formats changed files only. Pass `--scope=all` to run on every file the formatter handles:

```shell theme={null}
aspect format                     # --scope=changed (default)
aspect format --scope=all //...   # every file the formatter knows how to handle
```

`aspect format` detects changed files in this order:

1. **Local `git diff <merge-base>`** — the default. Defaults to `--base-ref=origin/main`; override with `--base-ref` or pass an explicit `--merge-base=<sha>`.
2. **GitHub PR Files API** — fallback when `git diff` can't resolve the diff base (shallow clones, fetch-depth-restricted runners), on a PR with the [Aspect Workflows GitHub App](/docs/cli/authentication-github) authenticated.

### Detection: when does `aspect format` decide formatting is required?

Most rules\_lint formatters rewrite files in place and exit 0 even when they made changes. So "did the formatter exit non-zero?" isn't a useful signal. `aspect format` instead **takes a tree snapshot before running the formatter, then diffs against that snapshot afterward**:

```
pre_snap = git stash create        # SHA, or "" if working tree was clean
run formatter
formatter_diff = git diff <pre_snap | HEAD>
if formatter_diff is non-empty:
    formatting required → fail (or warn under --soft-fail)
```

`git stash create` is non-destructive — it writes a commit object to the object store and returns its SHA without touching the working tree, the index, or the stash list. As a result, **the detection works correctly on a dirty local clone**: any pre-existing changes are absorbed into the snapshot and don't false-positive as "formatter modified".

### Soft-fail

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

```yaml theme={null}
tasks:
  format:
    soft_fail: true
```

**After** — `--soft-fail` (default `false`):

<CodeGroup>
  ```shell CLI theme={null}
  aspect format --soft-fail
  ```

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

When `--soft-fail` is set and the formatter modified files, the task prints a `WARNING` listing the affected files and exits 0. Useful for teams introducing automated formatting incrementally so the CI step doesn't block merges. The formatter binary's own non-zero exit (e.g. config error) still fails the task regardless.

### Diff archival

**Before** — `.aspect/workflows/config.yaml`: the legacy task auto-archived `format.diff` as a CI artifact and pointed an annotation at it.

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

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

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

When the formatter modified files, the task writes the diff to a job-scoped tmpfile and uploads it as `format.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["format_diff"]` so future status-check features can link to it.

No-op when the working tree is clean post-format, or when not running on a recognized CI host.

### Ignore patterns / workspace awareness

The legacy `ignore_patterns: [glob, …]` excluded matching files from the diff outcome; `respect_workspaces: true` filtered changed files to the current Bazel workspace. **Both map to `--ignore-pattern`** in the new task:

<CodeGroup>
  ```shell CLI theme={null}
  aspect format \
    --ignore-pattern='vendor/**' \
    --ignore-pattern='**/*.generated.go' \
    --ignore-pattern='nested-bazel-workspace/**'
  ```

  ```python .aspect/config.axl theme={null}
  def config(ctx: ConfigContext):
      ctx.tasks["format"].args.ignore_patterns = [
          "vendor/**",
          "**/*.generated.go",
          "nested-bazel-workspace/**",
      ]
  ```
</CodeGroup>

Pattern syntax: `*` (one segment), `**` (zero or more segments), `?` (one char). Patterns are case-sensitive and matched against repo-relative file paths.

In `--scope=changed` (default), ignored paths are dropped from the file list passed to the formatter, so it never runs on them.

In `--scope=all`, the formatter walks the tree itself, so ignored files may still be rewritten on disk; ignored paths are excluded only from the post-format diff used to decide whether the task fails. The legacy `format` task had the same constraint — `ignore_patterns` filtered the failure-decision diff but couldn't stop the formatter from rewriting files it discovered itself — so this isn't a regression. If you need a hard guarantee the formatter never touches a directory in `--scope=all`, configure exclusion at the formatter level too (most rules\_lint helpers accept include/exclude lists).

<Note>
  The "nested Bazel workspace" case (where `aspect format` from the parent would otherwise stomp the child's files) is a common motivator for `--ignore-pattern`. List each nested workspace's directory and the parent's formatter leaves them untouched.
</Note>

## Examples

### Standard format check on PRs

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

```yaml theme={null}
tasks:
  format:
    target: //tools/format:format
    format_changed: true
```

**After** — CI configuration:

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

  jobs:
    format:
      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: Format
          run: aspect format --task:name format
  ```

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

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

  format:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script:
      - aspect format --task:name format
  # 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:
    format:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            name: Format
            command: aspect format --task:name format

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

### Soft-fail (annotate but don't fail)

For teams introducing automated formatting incrementally, downgrade the failure to a warning so the CI step doesn't block merges:

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  jobs:
    format:
      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: Format (soft-fail)
          run: aspect format --task:name format --soft-fail
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":hammer_and_wrench: Format (soft-fail)"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect format --task:name format --soft-fail
      agents:
        queue: aspect-default
  ```

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

  format:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script:
      - aspect format --task:name format --soft-fail
  # 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:
    format:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            name: Format (soft-fail)
            command: aspect format --task:name format --soft-fail

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

### Format the whole tree (`--scope=all`)

Useful for nightly maintenance jobs or when bringing a new formatter into a repo for the first time:

```shell theme={null}
aspect format --task:name format-all --scope=all //...
```

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

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

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  jobs:
    format:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        id-token: write
      env:
        ASPECT_API_TOKEN: ${{ secrets.ASPECT_API_TOKEN }}
      steps:
        - uses: actions/checkout@v6
        - name: Format
          run: aspect format --task:name format --upload-format-diff
  ```

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

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

  format:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    script:
      - aspect format --task:name format --upload-format-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:
    format:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            command: aspect format --task:name format --upload-format-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["format_diff"]` for downstream status-check or annotation features to surface.
