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

# aspect lint

> Run aspect lint with rules_lint aspects, SARIF diagnostics, the hold-the-line strategy, GitHub PR annotations, and Bazel-cached lint results in CI.

`aspect lint` runs one or more [`aspect_rules_lint`](https://registry.bazel.build/modules/aspect_rules_lint) aspects as a standard Bazel build, reads SARIF diagnostic output, and decides whether to fail the CI step based on which findings are in scope. It posts inline review comments on the diff via the `GithubLintComments` (GitHub) and `GitlabLintComments` (GitLab) features.

The key design: **`aspect lint` doesn't re-implement linting**. It drives Bazel, which caches lint results the same as any other action. If a file hasn't changed, its lint results come from the remote cache rather than running the linter again. At scale, `aspect lint //...` on a large repo is fast because Bazel only re-lints what's actually changed.

## Hold-the-line strategy

The default failure strategy — `hold-the-line` — is what makes lint practical on large, pre-existing codebases with known violations.

Instead of failing on any finding anywhere, `hold-the-line`:

1. Detects the PR's changed lines (via `git diff`, or the GitHub PR Files API as fallback)
2. Fails only on **error-severity findings on lines you actually changed**

This means: existing violations in untouched files are visible but don't block the PR. New violations introduced by the PR do. Teams adopting lint don't have to clean up the entire codebase first.

Four strategies are available:

| Strategy                  | When it fails                                      |
| ------------------------- | -------------------------------------------------- |
| `hold-the-line` (default) | Error-severity findings on changed lines           |
| `hold-the-file`           | Error-severity findings anywhere in a changed file |
| `hard`                    | Any error-severity finding in any linted target    |
| `soft`                    | Never (report only)                                |

```shell theme={null}
aspect lint --strategy=hold-the-line //...   # default
aspect lint --strategy=hard //...            # zero-tolerance
aspect lint --strategy=soft //...            # informational only
```

## Configuration

### Lint aspects

`aspect lint` requires at least one `--aspect` pointing to an [`aspect_rules_lint`](https://registry.bazel.build/modules/aspect_rules_lint) aspect:

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

Declare them in `config.axl` so developers running `aspect lint //...` locally use the same set as CI:

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

CLI flags override `config.axl`. A CI job that needs only fast linters can pass `--aspect=` directly without affecting the default config.

### Apply auto-fix patches

Some rules\_lint linters produce fix patches. Pass `--fix` to apply them to the working tree:

```shell theme={null}
aspect lint --fix //...
```

Useful in local dev and in automated "fix and commit" CI flows.

### Changed-file scope

By default, `aspect lint` runs on all targets and strategy filtering determines which findings fail. To restrict the Bazel build to targets in changed packages:

```shell theme={null}
aspect lint --base-ref=origin/main //...
```

Override `--base-ref` when your main branch isn't `origin/main`.

## GitHub PR annotations

`GithubLintComments` posts inline PR review comments for every error-severity finding that holds the line (i.e., would have caused a failure under `hold-the-line`). Reviewers see the issue directly on the diff line without switching to CI logs.

Enable by authenticating the [Aspect Workflows GitHub App](/docs/cli/authentication-github) and setting `ASPECT_API_TOKEN`. The feature is always enabled — it just no-ops when not authenticated.

Suggestions from auto-fix linters become one-click "commit suggestion" buttons in the PR review.

## GitLab MR comments

`GitlabLintComments` is the GitLab counterpart: it posts each finding as a position-bound discussion on the merge-request diff (the GitLab equivalent of a GitHub PR review comment), so reviewers see the issue inline in the MR's **Changes** view.

Enable by authenticating the [Aspect Workflows GitLab App](/docs/cli/authentication-gitlab) and setting `ASPECT_API_TOKEN`. The feature is always enabled — it no-ops when not in a merge-request pipeline or when not authenticated.

It mirrors the GitHub feature, with three GitLab-specific behaviors:

* **Suggestion blocks** — auto-fix patches render as GitLab ` ```suggestion ` blocks reviewers apply with one click.
* **Dedup across runs** — re-running the lint task replaces a stale discussion at the same position instead of stacking duplicates.
* **Thread auto-resolve** — on a clean pass (the task finds nothing), discussions Aspect opened on prior runs are marked **Resolved** rather than deleted, preserving the review history. (GitHub PR review comments have no native resolve, so the GitHub path deletes instead.)

Cap the number of discussions per run with `--gitlab-lint-comments:max-pr-comments=<n>` (default 25; `0` disables posting while keeping the lint exit code intact). Errors are posted first, then warnings, then notes; any excess is dropped from the MR but still drives the lint verdict.

## Routing findings: `findings_destination`

Where findings land is controlled by the shared `LintTrait.findings_destination` knob (applies to both the GitHub and GitLab features). The default is `auto`: auto-fix suggestions post as inline comments, while other findings post as check-run annotations on GitHub — and on GitLab, which has no per-finding annotation surface, `auto` posts only the auto-fix suggestions.

To post **every** finding as an inline comment (recommended on GitLab so non-fix findings show up too), set it to `comments` in `.aspect/config.axl`:

```python title=".aspect/config.axl" theme={null}
load("@aspect//traits.axl", "LintTrait")

def config(ctx: ConfigContext):
    ctx.traits[LintTrait].findings_destination = "comments"
```

Accepted values:

| Value              | Behavior                                                                                                         |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `auto` *(default)* | Fix-bearing findings → inline comments; the rest → check-run annotations (GitHub) or no inline surface (GitLab). |
| `comments`         | Every finding → inline comment (GitHub PR review comment / GitLab MR discussion).                                |
| `annotations`      | Every finding → check-run annotation; no inline comments. Suggestion blocks are dropped.                         |
| `both`             | Every finding posted to both surfaces.                                                                           |

<Note>
  The config hook is named `config` (`def config(ctx: ConfigContext):`) — a function named `configure` is **not** invoked by the CLI and silently does nothing.
</Note>

## How it works under the hood

`aspect lint` uses two Bazel output groups from [`aspect_rules_lint`](https://registry.bazel.build/modules/aspect_rules_lint):

* **`rules_lint_machine`** — SARIF JSON and per-target exit codes. Used for CI verdicts and annotation posting.
* **`rules_lint_human`** — Colored terminal output. Streamed to the developer's terminal.

After the Bazel build completes, the task downloads SARIF files from the output groups (polling for up to 30s to allow remote cache downloads to finish) and processes them:

1. Reads every diagnostic from every SARIF file
2. Computes the changed-line set for the PR (`git diff`, or the GitHub PR Files API as fallback)
3. Filters diagnostics according to the chosen strategy
4. For `hold-the-line`, uses `±3 line context` around each changed hunk to catch findings that are technically on adjacent-but-related lines
5. Posts inline comments for each finding that passed the filter — via `GithubLintComments` on GitHub, `GitlabLintComments` on GitLab
6. Exits 0 or 1 based on whether any filtered findings have error severity

Because lint results go through Bazel's action cache, re-running `aspect lint` after an unrelated change doesn't re-execute any linter whose inputs haven't changed. On Aspect Workflows with remote cache, lint is O(changed targets), not O(repo size).

## CI examples

<CodeGroup>
  ```yaml GitHub Actions theme={null}
  on:
    pull_request:
      branches: [main]

  jobs:
    lint:
      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 }}
        - run: aspect lint --task:name lint //...
  ```

  ```yaml Buildkite theme={null}
  steps:
    - label: ":lint-roller: Lint"
      plugins:
        - aspect-build/setup-aspect#1d5768c5d28b72bf523b4722fc9177d2cc2d85c7: ~ # v2026.26.8
      command: aspect lint --task:name lint //...
      agents:
        queue: aspect-default
  ```

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

  lint:
    extends: .setup-aspect
    tags: [aspect-workflows, aspect-default]
    rules:
      - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
    script: aspect lint --task:name lint //...
  # Set ASPECT_API_TOKEN as a masked CI/CD variable.
  ```

  ```yaml CircleCI theme={null}
  version: 2.1

  orbs:
    setup-aspect: aspect-build/setup-aspect@2026.26.10

  jobs:
    lint:
      machine: true
      resource_class: circleci-org/aspect-default
      steps:
        - checkout
        - setup-aspect/setup
        - run: aspect lint --task:name lint //...
  ```
</CodeGroup>
