format task (Workflows 5.x) ran a 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. Whether modified files fail the step is set by --severity: the default auto fails on CI and only reports off CI.
What changed
Configuration
Most settings below can be applied either way:- 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 everyaspect formatinvocation picks it up. Good for settings that should apply to all invocations of a task type.
Formatter target
Before:.aspect/workflows/config.yaml:
config.axl (only needed if your formatter target isn’t at the new default //tools/format):
The default formatter target moved from
//:format to //tools/format. Putting format_multirun in a sibling package keeps its load(…) out of your root BUILD.bazel, so commands that touch the root package don’t fetch and analyze format-only machinery.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 don’t need to set the flag at all.Scope: changed files vs. the whole tree
Before:.aspect/workflows/config.yaml:
--scope=all to run on every file the formatter handles:
aspect format detects changed files in this order:
- Local
git diff <merge-base>: the default. With no--base-ref, the base is the CI host’s PR or MR target branch (GITHUB_BASE_REF,BUILDKITE_PULL_REQUEST_BASE_BRANCH,CI_MERGE_REQUEST_TARGET_BRANCH_NAME), then the remote’s default branch, thenorigin/main. Override with--base-ref, or pass an explicit--merge-base=<sha>. - GitHub PR Files API: fallback when
git diffcan’t resolve the diff base (shallow clones, fetch-depth-restricted runners), on a PR with the Aspect Workflows GitHub App 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:
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 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:
--severity=warn (default auto, which is fail on CI and info off it):
--severity=warn, when the formatter modified files the task reports a warning status and exits 0. --severity=info passes outright. 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, on by default:
format.patch via the same uploader build/test use for testlogs. Works on GitHub Actions, Buildkite, CircleCI, and GitLab. Turn it off with --upload-format-diff=false.
No-op when the working tree is clean post-format, or when not running on a recognized CI host.
Ignore patterns / workspace awareness
The legacyignore_patterns: [glob, …] excluded matching files from the diff outcome; respect_workspaces: true filtered changed files to the current Bazel workspace. Both map to --exclude-pattern in the new task:
* (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. 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).
The “nested Bazel workspace” case (where
aspect format from the parent would otherwise stomp the child’s files) is a common motivator for —exclude-pattern. List each nested workspace’s directory and the parent’s formatter leaves them untouched.Other flags with no Rosetta equivalent
aspect buildifier builds its recipe out of these.
Examples
Standard format check on PRs
Before:.aspect/workflows/config.yaml:
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:Format the whole tree (--scope=all)
Useful for nightly maintenance jobs or when bringing a new formatter into a repo for the first time:
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:

