Adopt a baseline on an existing codebase
If your project already has thousands of accessibility findings, you do not have to fix them before
you start gating. Record what exists today as a baseline, commit it, and b8e ci fails the build
only on findings that are not in it. Existing debt stays visible and stops blocking; anything new
is caught from the first pull request.
Record what you have today
Section titled “Record what you have today”From the project root:
b8e baseline .binclusive baseline — snapshotted 1284 finding(s) → binclusive-baseline.jsonThe snapshot is unscoped. It runs the same multi-stack scan the gate runs, over the whole
project, with no --base and no severity filter — so it records warn-level findings as well as
block-level ones. That is deliberate: the day you raise enforcement with b8e ci --enforce warn,
or move a success criterion into your block list, the debt that was only advisory yesterday is
already accounted for and cannot surprise the build.
Source findings only. b8e baseline runs the static source collectors and nothing else — the
same set b8e ci runs. It never opens a browser, so findings from a live-page scan
(b8e scan --url) are not in the snapshot. That is not a gap you need to close: b8e ci does not
scan URLs either, so there is nothing on that side for a baseline to grandfather. Baselining is for
the source backlog, which is the backlog a build gates on.
The file lands at <dir>/binclusive-baseline.json, beside binclusive.json. Write it somewhere
else with --output:
b8e baseline . --output config/a11y-baseline.jsonb8e baseline needs a credential to run, the same as every other b8e command: a b8e auth login
session, or a BINCLUSIVE_API_KEY. With neither, the command exits 7 and tells you to sign in.
Holding a credential does not mean anything is sent. b8e baseline reads your source and writes one
file; it uploads nothing, with or without a key.
Commit it
Section titled “Commit it”git add binclusive-baseline.jsongit commit -m "chore: baseline current accessibility findings"Treat it as a source file, not a build artifact: it belongs in review and it belongs in diffs.
Entries are sorted by fingerprint, so re-running b8e baseline . with no source change rewrites the
same bytes and produces an empty git diff.
Gate on it
Section titled “Gate on it”b8e ci auto-detects binclusive-baseline.json next to the directory it scans. If the file is at
that default path, your existing workflow step needs no change at all:
b8e ci --base origin/mainPass --baseline only when the file lives somewhere else:
b8e ci --base origin/main --baseline config/a11y-baseline.jsonThe run says how much the baseline absorbed:
binclusive ci — 12 file(s) under /repo, 37 finding(s), 34 baselined, 3 gating (enforce: block)What gates and what does not
Section titled “What gates and what does not”- A finding in the baseline is subtracted before the gate. It does not fail the build and it
does not count toward
--max-violations. It is not hidden, though: it is still scanned, it is counted in the run’s finding total and in theN baselinedtally, and it is still uploaded to your dashboard. What it loses is its line in the gating list and its vote on the exit code. - Where it shows up in machine-readable output depends on whether that surface remembers:
--format jsoncarries it plain — the export is the whole set.--format sarifcarries it marked suppressed, so a code-scanning alert stays honest rather than reading as fixed.--format githubomits it — an inline annotation asks you to act now, and accepted debt re-annotated on every pull request is the noise a baseline exists to remove.
- A finding not in the baseline gates exactly as it would with no baseline at all.
- A finding the scanner cannot locate — and therefore cannot fingerprint — is never subtracted. Baseline mode only ever removes proven matches; it never waves through an unmatched finding.
Two scopes are in play and they are not the same one. The baseline is whole-project, because a
partial snapshot would record only part of your debt. b8e ci --base <ref> is changed-file scoped.
A gating run therefore sees the findings in the files that pull request touched, minus everything
the baseline already knew about.
What happens when the code moves
Section titled “What happens when the code moves”A baseline entry is { ruleId, location } and nothing else. Every entry is a source finding, so
the location is the repo-relative path, a hash of the offending line’s content, and an index that
separates identical lines within one file. It is deliberately not file:line.
Entries do also carry a line number, and it is easy to misread when you open the file: it is
recorded for display, so a tool has something to link to. Nothing matches on it, and it is allowed
to go stale. So:
- Lines shift — you add an import above it, or reformat the file. Still matched, still grandfathered. This is the case the design is for.
- The offending line’s content changes. No longer matched. If the finding still fires it gates as new, which is right: you edited that line and did not fix it.
- The file is renamed, or the code moves to another file. No longer matched, so it gates. If a large move is not the moment to fix those findings, regenerate the baseline in the same commit.
A broken baseline is a crash, never a quiet pass
Section titled “A broken baseline is a crash, never a quiet pass”A baseline that cannot be trusted must not be allowed to disable the gate, so b8e ci refuses to
run rather than pass green:
- Unreadable file, invalid JSON, or a shape violation — the run fails with exit
2(crash), distinct from the1a findings failure exits with. Do not let acontinue-on-errorstep swallow it. --baseline <path>pointing at a file that is not there — an error. You asked for a baseline that does not exist.- No
--baselineand nobinclusive-baseline.json— the one tolerated empty state. The gate runs unchanged, as if no baseline existed, because none does.
See the exit-codes reference for the full contract.
Shrink it as you pay the debt down
Section titled “Shrink it as you pay the debt down”A baseline nobody ever reduces is technical debt with a green tick on top. The file is a static snapshot — it never learns that you fixed something, and an entry that outlives the finding it grandfathered will happily grandfather that finding’s reintroduction.
b8e ci tells you when that is happening. On a whole-project run — no --base — it counts the
entries that matched nothing:
BASELINE — 41 of 1284 recorded issues are already fixed run `b8e baseline .` to update the file — until you do, those issues won't fail the build if they come backThat is a report, not a gate: it never changes the exit code, and b8e ci will not rewrite a
committed file on your behalf.
It appears only on an unscoped run, and that is worth planning around. Under --base, an entry
elsewhere in the repo matched nothing because the run never looked at it — calling that “already
fixed” would be a lie, printed on every pull request. So run b8e ci with no --base on a schedule
(nightly, or on pushes to your default branch) and read the BASELINE line as your burn-down
signal.
When it fires, regenerate and commit:
b8e baseline .git add binclusive-baseline.jsongit commit -m "chore: prune fixed findings from the a11y baseline"The diff is the debt you paid.
A baseline is not a suppression list
Section titled “A baseline is not a suppression list”binclusive.json has hand-authored escape hatches — learned rules, and the components
declarations that pin a wrapper to its host element. You write those by intent, one at a time,
because you decided a specific thing should be treated differently, and you expect them to stay.
A baseline is the opposite: bulk, machine-generated, and meant to be burned down. The two never
mix. b8e baseline never writes into binclusive.json, and no baseline entry is ever folded into
learned — which is what keeps deliberate suppression legible next to grandfathered debt. The
committed file says so in its own note field.
If what you actually want is to teach the scanner about a component it cannot resolve, that is Declare a component the scanner can’t resolve, not a baseline.
For every flag these commands accept, see the baseline
reference and the ci reference.