CI
Fail a pull request only on violations the change introduces. Flags, exit codes, and CI snippets for GitHub Actions, GitLab CI, and Azure Pipelines.
equall scan . --diff [base] --fail-on <severity>Compares the change against its base and reports only what the change introduced. Existing debt ("legacy" findings) never fails the check. Identity is a fingerprint, not the line number, so a reformat doesn't read as new.
Nothing leaves your runner: the scan is static, runs on the checked-out code, no account, no token, nothing uploaded.
This is a static check of the source. It never replaces a check of the rendered page — the terminal output says so too: "The rendered page still needs its own check."
Flags
| Flag | Description | Default |
|---|---|---|
--diff [base] | Git ref to compare against, e.g. origin/main. With no value, read from the CI environment: CI_MERGE_REQUEST_DIFF_BASE_SHA (GitLab), then GITHUB_BASE_REF (GitHub Actions), CI_MERGE_REQUEST_TARGET_BRANCH_NAME (GitLab), SYSTEM_PULLREQUEST_TARGETBRANCH (Azure Pipelines) — a branch resolves to origin/<branch> | (off) |
--fail-on <severity> | Exit 1 when the change introduces a violation at this severity or above: critical, serious, moderate, minor | (off — report only) |
--fail-on only works with --diff. --diff can't be combined with --min-score. Without --fail-on, --diff only reports and always exits 0.
equall scan <dir> --diff limits the diff to <dir>. Paths stay relative to the repository root.
With --diff, --level sets the target level (default AA) and --json prints the diff result. The full-scan options --include, --exclude, --standard and --no-readability are not used: the diff scans every changed .html, .htm, .jsx, .tsx, .vue, .svelte and .astro file, minus the files a full scan skips by default.
Start with --fail-on critical; switch to serious to be stricter.
Exit codes
These apply to --diff. A full scan (without --diff) follows the exit codes on Scanning.
| Exit | Meaning |
|---|---|
0 | No new violation at the threshold, or --diff used without --fail-on (report only) |
1 | The change introduced a violation at --fail-on or above |
2 | The check couldn't run: invalid --fail-on value, --fail-on without --diff, --min-score with --diff, no base detected, base ref not fetched, or a shallow clone |
What blocks the merge
- New WCAG violations at or above
--fail-on.
What's reported but never blocks
- New violations below the threshold — a warning.
- New review-only findings — static analysis can't confirm them, flagged "to review".
- New advisory findings — best practice, or above the target conformance level.
- Legacy findings already present in the changed files at the base.
- Changed files that can't be statically tested (CSS, scripts, config) — the count is named in the output.
- Changed files a scanner couldn't analyze — listed as "not checked", since their findings are unknown.
- Files a full scan also skips (tests, stories, build output).
--json prints the diff result as JSON: new_issues, new_review_only, new_advisory, legacy_issues, not_testable, unchecked, excluded, summary.
GitHub annotations
On GitHub Actions, the check writes annotations on the pull request: errors for new violations at the threshold, warnings below it, notices for review-only and advisory findings, warnings for files not checked. Findings without a line number (axe, markup-level) are annotated at file level and appear in the check summary instead of inline.
GitHub caps annotations at 10 errors and 10 warnings per step. The job summary and the terminal output list everything.
History and shallow clones
The comparison needs the base branch and the history it shares with the change. Without it, the check exits 2 with a message naming what to fetch.
- GitHub Actions —
actions/checkoutwithfetch-depth: 0. - GitLab CI —
GIT_DEPTH: 0. The base is the merge-base commit GitLab computes (CI_MERGE_REQUEST_DIFF_BASE_SHA). - Azure Pipelines —
checkout: selfwithfetchDepth: 0.
CI snippets
GitHub Actions
Trigger on pull_request, not pull_request_target — the latter checks out the base branch, so the diff is empty.
on: pull_request
jobs:
a11y:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npx equall-cli@^0.3.3 scan . --diff --fail-on criticalA ready-made GitHub Action is coming; this snippet is the fallback until then.
GitLab CI
a11y:
image: node:22
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
variables:
GIT_DEPTH: 0
script:
- npx equall-cli@^0.3.3 scan . --diff --fail-on criticalAzure Pipelines
pr:
- main
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
fetchDepth: 0
- task: NodeTool@0
inputs:
versionSpec: '22.x'
- script: npx equall-cli@^0.3.3 scan . --diff --fail-on criticalGeneric shell / any CI
git fetch origin main
npx equall-cli@^0.3.3 scan . --diff origin/main --fail-on criticalUse the exit code to gate the build.