Skip to main content
Rosetta’s 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 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:
After: CLI flag or 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:
After: default behavior already formats changed files only. Pass --scope=all to run on every file the formatter handles:
aspect format detects changed files in this order:
  1. 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, then 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 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:
After: --severity=warn (default auto, which is fail on CI and info off it):
With --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:
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. 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 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 --exclude-pattern in the new task:
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. 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:
After: CI configuration:

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: