Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.aspect.build/llms.txt

Use this file to discover all available pages before exploring further.

aspect lint runs one or more 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 PR annotations via the GithubLintComments feature. 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 GitHub PR Files API or git diff)
  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:
StrategyWhen it fails
hold-the-line (default)Error-severity findings on changed lines
hold-the-fileError-severity findings anywhere in a changed file
hardAny error-severity finding in any linted target
softNever (report only)
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 aspect:
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:
.aspect/config.axl
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:
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:
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 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.

How it works under the hood

aspect lint uses two Bazel output groups from 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 (GitHub PR Files API or git diff)
  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 annotations via GithubLintComments for each finding that passed the filter
  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

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@c22a8f64fb38f82f59ce809cd7eb9f8ae096da44 # v2026.23.2
        with:
          aspect-api-token: ${{ secrets.ASPECT_API_TOKEN }}
      - run: aspect lint --task-key lint //...