> ## Documentation Index
> Fetch the complete documentation index at: https://aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# aspect lint

> Migrate the Workflows 5.x Rosetta lint task to aspect lint with rules_lint aspects, SARIF diagnostics, and GithubLintComments PR annotations from the Aspect CLI.

Rosetta's `lint` task (Workflows 5.x) ran `bazel lint` with environment-specific flags injected automatically, parsed a proprietary JSON diagnostics file, and posted GitHub annotations directly from the task runner. `aspect lint` replaces it.

`aspect lint` runs a standard Bazel build with [rules\_lint](https://github.com/aspect-build/rules_lint) aspects enabled, reads SARIF output, and exposes lifecycle hooks for custom reporting.

<Note>
  GitHub PR annotations are no longer baked into the task. They're posted by the **<code>GithubLintComments</code>** feature, which authenticates via the [Aspect Workflows GitHub App](/docs/cli/authentication-github) and is enabled by default on GitHub Actions.
</Note>

## What changed

| Area | YAML-configured tasks | Aspect CLI tasks |
| - | - | - |
| Invocation | `bazel lint` (legacy Aspect CLI command); aspects configured in `.aspect/cli/config.yaml` (`lint.aspects:`) | `aspect lint`; aspects configured per-invocation via `--aspect=//tools/lint:linters.bzl%eslint` (repeatable) or in `.aspect/config.axl` (`ctx.tasks["lint"].args.aspects = […]`) |
| Diagnostics format | Proprietary JSON (`--lint_diagnostics_file`) | SARIF (via `rules_lint_machine` output group) |
| GitHub annotations | Built into the task | `GithubLintComments` feature via the Aspect Workflows GitHub App (on by default) |
| Configuration | `.aspect/workflows/config.yaml` task block | `config.axl` / CLI flags |
| Failure strategy | `failure_strategy` string | `--strategy` CLI flag |
| Deduplication | Built-in (highest severity wins) | Handled by `rules_lint` |
| Annotation / comment limit | 25 annotations, 10 suggestions | `GithubLintComments` posts at most 25 comments per run (`--github-lint-comments:max-pr-comments`) |
| Fix application | `--fix` via task config | `aspect lint --fix` |
| Diff base | `git diff HEAD^..HEAD` | `git diff` against the merge-base of `HEAD` and `--base-ref` (default: the CI host's PR or MR target branch, then the remote's default branch, then `origin/main`), with the GitHub PR Files API (Aspect Workflows GitHub App authenticated) as fallback when `git diff` can't resolve the base |
| [Workspaces](/docs/cli/migration#top-level-workspaces) | `workspaces:` top-level list (one or more directories) | `cd <dir>` in your CI config; run `aspect lint` from there |

## Configuration

Most settings below can be applied either way:

* **CLI flag**: pass the flag at invocation time (e.g. `aspect lint --strategy=hard //...`). 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 lint` invocation picks it up. Good for settings that should apply to all invocations of a task type. AXL can also compute values (e.g. branching on CI host or environment).

Pick whichever fits; you only need one. Where both work, the sections below show them side-by-side under **After**.

### Lint aspects (`--aspect`)

In the legacy Aspect CLI, the `bazel lint` command picked up its [rules\_lint](https://github.com/aspect-build/rules_lint) aspects from the `lint.aspects` list in `.aspect/cli/config.yaml`:

```yaml theme={null}
lint:
  aspects:
    - //tools/lint:linters.bzl%buf
    - //tools/lint:linters.bzl%eslint
    - //tools/lint:linters.bzl%vale
    - //tools/lint:linters.bzl%tfsec
```

`aspect lint` makes the aspect list explicit at the task site. **At least one rules\_lint aspect must reach Bazel** — via `--aspect=<label>`, `.aspect/config.axl`, Bazel's own `--aspects=`, or a bazelrc `--config` — but `--aspect` itself is optional, and the CI examples below leave it out because they declare their aspects in `config.axl`. Repeat the flag to run multiple aspects in a single invocation; each `--aspect=<label>` becomes a Bazel `--aspects=<label>` on the underlying build.

**Recommended: declare aspects in `.aspect/config.axl`** so the same `aspect lint` invocation runs the same aspects locally as in CI, and your CI step doesn't enumerate them:

```python .aspect/config.axl theme={null}
def config(ctx: ConfigContext):
    ctx.tasks["lint"].args.aspects = [
        "//tools/lint:linters.bzl%buf",
        "//tools/lint:linters.bzl%eslint",
        "//tools/lint:linters.bzl%vale",
        "//tools/lint:linters.bzl%tfsec",
    ]
```

The CLI flag is also fine for one-off invocations, parallel CI jobs that need *different* aspect sets, or experimenting with a new linter before committing it:

```shell theme={null}
aspect lint \
  --aspect=//tools/lint:linters.bzl%buf \
  --aspect=//tools/lint:linters.bzl%eslint \
  --aspect=//tools/lint:linters.bzl%vale \
  --aspect=//tools/lint:linters.bzl%tfsec \
  //...
```

CLI flags override `.aspect/config.axl` defaults, so a job that wants only the fast linters can pass `--aspect=//tools/lint:linters.bzl%shellcheck` and skip the rest.

`linters.bzl` defines the aspects via rules\_lint helpers (e.g. `lint_shellcheck_aspect`, `lint_eslint_aspect`). See the [rules\_lint setup docs](https://github.com/aspect-build/rules_lint) for the canonical pattern, or the [aspect-cli lint example](https://github.com/aspect-build/aspect-cli/tree/main/examples/lint) for a minimal working setup.

### Failure strategy

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

```yaml theme={null}
tasks:
  lint:
    failure_strategy: hold_the_line   # or: soft, hard
```

**After**: CLI flag or `config.axl`:

<CodeGroup>
  ```shell CLI theme={null}
  aspect lint --strategy=hold-the-line   # default
  aspect lint --strategy=hard
  aspect lint --strategy=soft
  ```

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

Strategy reference:

| Legacy `failure_strategy` | New `--strategy` | Behavior |
| - | - | - |
| `hold_the_line` (default) | `hold-the-line` (default) | Fail only on error-severity diagnostics inside a changed diff hunk. `hold-the-file` widens that to the whole changed file. "Changed" is resolved as in the Diff base row of [What changed](#what-changed). |
| `hard` | `hard` | Fail on any error-severity diagnostic across all linted files; also fails if a linter exits non-zero |
| `soft` | `soft` | Never fail; diagnostics are surfaced but don't affect exit code |

### Lint targets

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

```yaml theme={null}
tasks:
  lint:
    targets:
      - //services/...
      - //libs/...
```

**After**: positional CLI args or `config.axl`:

<CodeGroup>
  ```shell CLI theme={null}
  aspect lint --aspect=//tools/lint:linters.bzl%eslint //services/... //libs/...
  ```

  ```python .aspect/config.axl theme={null}
  def config(ctx: ConfigContext):
      ctx.tasks["lint"].args.targets = ["//services/...", "//libs/..."]
  ```
</CodeGroup>

The default (no arguments) is `...`, which expands to all rule targets in the package at and beneath your current directory. To lint the entire workspace explicitly, run `aspect lint //...` from the repo root (equivalent to the legacy `targets: ['//...']`).

## GitHub PR review comments

On a GitHub pull request build, the **`GithubLintComments`** feature posts lint findings to the PR as inline review comments. It is **enabled by default**, so no extra configuration is needed beyond authenticating the runner (see [Authentication](#authentication) below).

What it does:

* Posts inline review comments on the lines of the PR diff that the linter flagged.
* Includes fix suggestions as GitHub suggestion blocks where the linter provides them, so reviewers can apply the fix with one click.
* Deduplicates comments across re-runs of the lint task on the same PR, and cleans up stale ones once a violation is fixed.

<Tip>
  <code>GithubLintComments</code> computes per-line changed regions the same way as the lint verdict (see the Diff base row of [What changed](#what-changed)). Override with <code>--merge-base=\<sha></code> to skip the merge-base resolution.
</Tip>

### Authentication

`GithubLintComments` needs:

* An Aspect credential: `aspect-api-token` on GitHub Actions, a Buildkite secret (`secrets: [ASPECT_API_TOKEN]`), `ASPECT_API_TOKEN` in the environment on GitLab and CircleCI.
* The **Aspect Workflows GitHub App** installed on the GitHub org/repository and linked to your Aspect account, so the CLI can use the App to post the review comments.

See the [GitHub App](/docs/cli/authentication-github) page for installing and linking the app, and [Authenticating the Aspect CLI](/docs/cli/authentication) for generating the API token.

If no comments appear on a pull request build, search the log for `GitHub lint comments:`. A missing credential logs one line per `aspect lint` invocation naming what's missing, for example on GitLab or CircleCI:

```
GitHub lint comments: Authentication failed for acme/widgets — no Aspect credentials — set ASPECT_API_TOKEN (an API token from the Aspect Web UI) in the job env. Setup guide: https://aspect.build/docs/cli/authentication
```

Outside a pull request build (push events, local development) the feature has nothing to post to.

### Disabling

<CodeGroup>
  ```shell CLI theme={null}
  aspect lint --github-lint-comments:enabled=false //...
  ```

  ```python .aspect/config.axl theme={null}
  load("@aspect//feature/github_lint_comments.axl", "GithubLintComments")

  def config(ctx: ConfigContext):
      ctx.features[GithubLintComments].enabled = False
  ```
</CodeGroup>

## CI integration examples

### Post comments

Run `aspect lint` on pull requests so `GithubLintComments` can post annotations as review comments.

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  name: Lint

  on:
    pull_request:
      branches: [main]
    push:
      branches: [main]

  jobs:
    lint:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        contents: read    # actions/checkout
        id-token: write   # artifact uploads and the PR summary comment
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - name: Lint
          run: aspect lint --task:name lint //...
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":broom: Lint"
      plugins:
        - aspect-build/setup-aspect#8de9aed254d0699baf201f66e2076fc8c09f42b9: ~ # v2026.38.2
      command: aspect lint --task:name lint //...
      secrets:
        - ASPECT_API_TOKEN
      agents:
        queue: aspect-default
  ```

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

  lint:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    script:
      - aspect lint --task:name lint //...
  # 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.38.2

  jobs:
    lint:
      machine: true
      resource_class: YOUR-ORG/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            name: Lint
            command: aspect lint --task:name lint //...

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

### Auto commit fixes

Run `aspect lint --fix` on feature branches and push the resulting diff back so developers don't have to re-run locally. Each platform needs a push credential with write access to the branch.

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  jobs:
    lint:
      runs-on: [self-hosted, aspect-workflows, aspect-default]
      permissions:
        contents: write   # actions/checkout and pushing the fix commit
        id-token: write   # artifact uploads and the PR summary comment
      steps:
        - uses: actions/checkout@v6
        - uses: aspect-build/setup-aspect@ebca96eb49ef58c00d4226de8bd3a0813507dd87 # v2026.38.3
          with:
            aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
        - name: Lint with fix
          run: aspect lint --fix --task:name lint //...
        - name: Commit fixes
          uses: stefanzweifel/git-auto-commit-action@v5
          with:
            commit_message: "lint: apply automatic fixes"
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":broom: Lint (fix)"
      if: build.branch != "main"
      plugins:
        - aspect-build/setup-aspect#8de9aed254d0699baf201f66e2076fc8c09f42b9: ~ # v2026.38.2
      command: |
        aspect lint --fix --task:name lint //...
        if [ -n "$$(git status --porcelain)" ]; then
          git config user.email "buildkite@example.com"
          git config user.name "buildkite"
          git commit -am "lint: apply automatic fixes"
          git push origin HEAD:$$BUILDKITE_BRANCH
        fi
      secrets:
        - ASPECT_API_TOKEN
      agents:
        queue: aspect-default
  ```

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

  lint-fix:
    extends: .setup-aspect
    tags:
      - aspect-workflows
      - aspect-default
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script:
      - aspect lint --fix --task:name lint //...
      - |
        if [ -n "$(git status --porcelain)" ]; then
          git config user.email "gitlab@example.com"
          git config user.name "gitlab"
          git commit -am "lint: apply automatic fixes"
          git push "https://oauth2:${GITLAB_PUSH_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" HEAD:${CI_COMMIT_REF_NAME}
        fi
  # 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.38.2

  jobs:
    lint-fix:
      machine: true
      resource_class: YOUR-ORG/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run:
            name: Lint with fix
            command: |
              aspect lint --fix --task:name lint //...
              if [ -n "$(git status --porcelain)" ]; then
                git config user.email "circleci@example.com"
                git config user.name "circleci"
                git commit -am "lint: apply automatic fixes"
                git push origin HEAD:$CIRCLE_BRANCH
              fi
  # Set ASPECT_API_TOKEN as a project environment variable or context secret.
  ```
</CodeGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.