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 format runs your formatter (Prettier, gofmt, Black, clang-format, or any other) against changed files and detects whether the tree is clean. It works on any CI system without any CI-specific setup, and runs identically on a developer’s machine. The core value: format only what changed. On a large monorepo, formatting every file on every PR is prohibitively slow. aspect format detects the PR’s changed file set and passes only those files to the formatter, making format checks fast at any scale.

How change detection works

aspect format uses the most accurate signal available, in order:
  1. GitHub PR Files API — when running on a PR with the Aspect Workflows GitHub App authenticated. Returns the exact files GitHub considers changed, including renames and moves.
  2. git diff <merge-base> — fallback on any CI system. Defaults to --base-ref=origin/main; override with --base-ref if your default branch has a different name, or pass --merge-base=<sha> to use an explicit SHA.
Both paths return file paths relative to the repo root, which are passed directly to the formatter binary.

Reliable drift detection via git snapshot

Most formatters rewrite files in place and exit 0 even when they changed something. You can’t use the exit code to know whether formatting was required. aspect format solves this with a non-destructive git tree snapshot:
pre_snap = git stash create          # writes a commit object, returns its SHA
                                     # does NOT touch the working tree or stash list
run formatter
diff = git diff <pre_snap | HEAD>    # compare current tree to the pre-format snapshot
if diff is non-empty → formatting required
git stash create is the key: it’s non-destructive. It writes a tree object into the git object store and returns its SHA without modifying the working tree, the index, or the stash reflog. The snapshot captures any pre-existing dirty state, so if a developer has uncommitted work before running the formatter, those pre-existing changes don’t false-positive as “formatter modified something”.

Configuration

Formatter target

aspect format builds and runs a Bazel target that contains your formatter(s). The default is //tools/format:format (a format_multirun or similar target from aspect_rules_lint).
# Override if your formatter lives elsewhere
aspect format --formatter-target=//:format
.aspect/config.axl
def config(ctx: ConfigContext):
    ctx.tasks["format"].args.formatter_target = "//:format"

Scope: changed files vs. the whole tree

aspect format                # --scope=changed (default): only PR-changed files
aspect format --scope=all    # every file the formatter handles
--scope=all is useful for nightly maintenance jobs or when introducing a new formatter for the first time. In this mode the formatter walks the tree itself; changed-file detection is bypassed.

Reacting to formatter output

aspect format --on-change=fail    # default on CI: exits 1 if formatter modified files
aspect format --on-change=warn    # prints a warning, exits 0
aspect format --on-change=silent  # no output, exits 0 (still uploads patch if enabled)
.aspect/config.axl
def config(ctx: ConfigContext):
    ctx.tasks["format"].args.on_change = "warn"  # soft-fail during rollout
auto (the default) maps to fail on CI and silent locally.

File filters

.aspect/config.axl
def config(ctx: ConfigContext):
    ctx.tasks["format"].args.ignore_patterns = [
        "vendor/**",
        "**/*.generated.go",
        "third_party/**",
    ]
In --scope=changed mode, ignored paths are dropped from the file list before the formatter runs — the formatter never sees them. Pattern syntax: * (one path segment), ** (any number of segments), ? (one character); case-sensitive.

Patch upload

When the formatter modifies files, aspect format can upload the diff as a format.patch artifact so developers can apply it locally:
.aspect/config.axl
def config(ctx: ConfigContext):
    ctx.tasks["format"].args.upload_format_diff = True
The patch is uploaded via the same ArtifactUpload feature build/test use. The download URL is recorded on ArtifactsTrait.artifact_urls["format_diff"] so status-check features can surface a direct link.

CI examples

on:
  pull_request:
    branches: [main]

jobs:
  format:
    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 format --task-key format