Skip to main content
The legacy Rosetta buildifier task ran a Bazel target (defaulting to //:buildifier.check) that invoked buildifier in check mode against the workspace’s Starlark files, surfacing lint warnings and formatting diffs as CI annotations. There are two ways to replace it with Aspect CLI tasks:
  1. config.axl alias (recommended) — register aspect buildifier as a first-class CLI command via a format.alias() declaration in .aspect/config.axl. No BUILD target needed, no rules_lint dependency.
  2. BUILD target approach — declare a Starlark-only format_multirun target and point aspect format at it via --formatter-target.

What changed

AreaYAML-configured tasksAspect CLI tasks
Invocationrosetta run buildifieraspect buildifier (config.axl alias) or aspect format --formatter-target=//tools/format:format-starlark (BUILD target approach)
Check targettarget: (default //:buildifier.check)formatter_target default set in config.axl, or passed via --formatter-target on the CLI
Fix targetfix_target: (default //:buildifier)Both approaches run the fix target by default; the legacy “check then suggest fix” annotation is replaced by aspect format’s pre/post-snapshot diff flow
Lint warningsSurfaced as a separate annotation pointing at buildtools/WARNINGS.mdNot surfaced separately — buildifier’s -mode=fix applies what it can fix; warnings appear in aspect format’s output and the unified format check-run
Diff archivalReused the legacy format task’s diff handling when invoked via bazel runaspect format --upload-format-diff archives the patch as format.patch via ArtifactsTrait. See aspect format → Diff archival
Soft-failsoft_fail: true downgraded the failure to a warning--soft-fail. See aspect format → Soft-fail
Ignore patternsn/a--ignore-pattern (repeatable). See aspect format → Ignore patterns

This approach registers aspect buildifier as a real CLI command by declaring a format.alias() in .aspect/config.axl. It does not require a BUILD target or a rules_lint dependency — only buildifier_prebuilt.

Setup

MODULE.bazel:
bazel_dep(name = "buildifier_prebuilt", version = "8.5.1.2")  # or newer
.aspect/config.axl (create if it doesn’t exist):
load("@aspect//format.axl", "format")

buildifier = format.alias(
    defaults = {
        "formatter_target": "@buildifier_prebuilt//buildifier",
        # buildifier_binary is a symlink to the raw Go binary; it does not
        # switch to $BUILD_WORKING_DIRECTORY on its own, so we run it in
        # the user's cwd to make relative paths resolve.
        "run_in_cwd": True,
        # Fires only when the format task would otherwise pass zero files
        # to the formatter (e.g. `--scope=all`, or a `--scope=changed` run
        # that degraded because no changed files matched). The bare
        # buildifier binary needs `-r .` to walk the tree on its own. Inert
        # whenever a file list is passed, so `--scope=changed` (the default)
        # and per-file invocations are unaffected.
        "formatter_args_for_tree_walk": ["-r", "."],
        "include_patterns": [
            "**/BUILD",
            "**/BUILD.bazel",
            "**/MODULE.bazel",
            "**/*.MODULE.bazel",
            "**/WORKSPACE",
            "**/WORKSPACE.bazel",
            "**/*.axl",
            "**/*.bzl",
        ],
    },
    summary = "Format Starlark files using buildifier.",
)

def config(ctx: ConfigContext):
    ctx.tasks.add(buildifier)
format.alias() creates an aspect buildifier command pre-configured with a formatter_target and include_patterns. Running aspect buildifier is equivalent to running aspect format with those defaults baked in — only Starlark files are touched, no other formatters run.
ctx.tasks.add(buildifier) registers the alias as a CLI command. Until that line is present, aspect buildifier won’t exist.
--scope=all covers a narrower file set than --scope=changed. In --scope=all the format task hands off discovery to buildifier’s -r . tree walk, which recognizes only BUILD, BUILD.bazel, MODULE.bazel, WORKSPACE, WORKSPACE.bazel, *.bzl, *.star. Files matching the alias’s include_patterns but not on buildifier’s list — notably *.axl, *.MODULE.bazel — are silently skipped. The default --scope=changed passes the changed file list to buildifier positionally and is unaffected.See aspect buildifier → Run it locally for details.

CI configuration

Add a dedicated buildifier job alongside your other Aspect CLI tasks. If your repo also runs aspect format for other languages, run both jobs in parallel — they don’t overlap because include_patterns in the alias limits buildifier to Starlark files.
jobs:
  buildifier:
    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: Buildifier
        run: aspect buildifier --task:name buildifier

BUILD target approach

Use this approach if you prefer to keep buildifier wired through a Bazel target in tools/format/BUILD.bazel.

Wiring buildifier into your formatter target

The default formatter target is //tools/format:format. Add starlark = to its format_multirun invocation, pointing at a buildifier binary from buildifier_prebuilt: MODULE.bazel:
bazel_dep(name = "buildifier_prebuilt", version = "8.5.1.2")  # or newer
bazel_dep(name = "aspect_rules_lint", version = "...")  # required for format_multirun
tools/format/BUILD.bazel:
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")

format_multirun(
    name = "format",
    starlark = "@buildifier_prebuilt//:buildifier",
    # ...other languages your repo formats:
    # rust = "@rules_rust//tools/upstream_wrapper:rustfmt",
    # go = "@aspect_rules_lint//format:gofumpt",
    # javascript = ":prettier",
)
That’s the whole switch in the common case — aspect format will now run buildifier on Starlark files as part of its normal flow. No new task wiring, no new CI step required.
The legacy task’s target and fix_target (//:buildifier.check / //:buildifier) are no longer used. format_multirun produces both the fix target (//tools/format:format) and a check-only sibling (//tools/format:format.check) automatically; aspect format invokes the fix target and decides pass/fail by diffing the working tree before and after. You can delete the old labels once nothing references them.

Running buildifier as a dedicated task

Some repos want buildifier on its own CI step — a fast Starlark-only check that runs in parallel with (and finishes well before) the bigger language formatters. The legacy buildifier task gave that for free. The new pattern: declare a second format_multirun target that only sets starlark =, then point a dedicated aspect format invocation at it via --formatter-target. The default formatter target in tools/format:format keeps every other language; the Starlark-only target lives alongside it. tools/format/BUILD.bazel:
load("@aspect_rules_lint//format:defs.bzl", "format_multirun")

format_multirun(
    name = "format",
    starlark = "@buildifier_prebuilt//:buildifier",
    # ...other languages...
)

format_multirun(
    name = "format-starlark",
    starlark = "@buildifier_prebuilt//:buildifier",
)
Then in your CI, run two aspect format steps in parallel — one for buildifier and one for everything else. Use --task:name to give each a distinct status check name, and --ignore-pattern to keep them from doing duplicate work:
jobs:
  buildifier:
    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: Buildifier
        run: |
          aspect format \
            --task:name buildifier \
            --formatter-target=//tools/format:format-starlark

  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 (non-Starlark)
        run: |
          aspect format \
            --task:name format \
            --ignore-pattern='**/*.bzl' \
            --ignore-pattern='**/BUILD' \
            --ignore-pattern='**/BUILD.bazel' \
            --ignore-pattern='**/MODULE.bazel' \
            --ignore-pattern='**/WORKSPACE' \
            --ignore-pattern='**/WORKSPACE.bazel'
The same split generalizes — declare one format_multirun per group of formatters you want as a separate CI step (e.g. one for Starlark, one for JavaScript/TypeScript/CSS via Prettier, one for everything else), and use --ignore-pattern on the catch-all step to avoid re-formatting files the dedicated step already handled.
Pinning --formatter-target for a dedicated buildifier step at the CLI flag level — rather than in .aspect/config.axl — is intentional. config.axl is global to all aspect format invocations, so setting formatter_target there would also redirect your default aspect format (the one developers run locally). Per-CI-step overrides belong on the CLI. (This constraint does not apply to the config.axl alias approach, which registers a separate buildifier command rather than overriding format.)

Lint warnings: use aspect lint

The legacy task surfaced buildifier’s lint warnings (the --lint=warn output that lists rule violations like module-docstring, name-conventions, etc.) as a dedicated annotation pointing at buildtools/WARNINGS.md. In the new world, that lives in aspect lint, not aspect format. format_multirun (and the config.axl alias) runs buildifier in fix mode and auto-applies what it can; warnings buildifier won’t auto-fix don’t surface as a status check from aspect format. To gate on those warnings, wire buildifier in as a rules_lint lint aspect and run it from aspect lint. tools/lint/linters.bzl:
load("@aspect_rules_lint//lint:buildifier.bzl", "lint_buildifier_aspect")

buildifier = lint_buildifier_aspect(
    binary = Label("@buildifier_prebuilt//:buildifier"),
    # warnings = "all",  # default; or pass a comma-separated allowlist
)
By default the aspect visits bzl_library targets. To lint BUILD.bazel, MODULE.bazel, or other Starlark files, add tags = ["starlark"] (or tags = ["lint-with-buildifier"]) to a target listing them in srcs:
filegroup(
    name = "starlark_files",
    srcs = ["BUILD.bazel", "MODULE.bazel"],
    tags = ["starlark"],
)
Then add //tools/lint:linters.bzl%buildifier to your aspect lint invocation — either as --aspect on the CLI or in .aspect/config.axl. See the aspect lint migration guide for the wiring details. Output is SARIF, deduplicated and (on GitHub Actions, by default) posted as PR comments by the GithubLintComments feature. Recommended split:
ConcernTaskMode
Auto-applied formatting + simple fixesaspect buildifier or aspect format (Starlark via buildifier)--lint=fix (in-place edits)
Warnings that need human attentionaspect lint --aspect=…%buildifier--lint=warn (SARIF + PR comments)
This is the same split rules_lint uses for every other Starlark-aware tool — formatting in format_multirun, diagnostics in the lint aspect — so the buildifier wiring matches the rest of your linters.