Skip to content

Where in the Pipeline

A compatibility check can run at four moments in a library's life, and each one answers a different question at a different cost. This page places them, says what each catches, and explains the one rule that keeps them from fighting each other: a break caught after merge re-fails every unrelated pull request until the baseline is refreshed.

Four moments, two baselines

flowchart LR
    PR["Pull request<br/>(cheap tiers, seeded source depth)"] -->|merge| MAIN["Merge to main<br/>(refresh accepted-main baseline)"]
    MAIN --> NIGHTLY["Nightly<br/>(unseeded deep scan + one-build audit)"]
    MAIN --> REL["Release cut<br/>(publish release-contract baseline)"]
    B1[("accepted-main<br/>baseline")] -.->|compared at| PR
    MAIN -.->|writes| B1
    B2[("release-contract<br/>baseline")] -.->|compared at| REL
    REL -.->|writes| B2

The pull request asks "does this change break the contract we accepted so far?" — against the accepted-main baseline, the snapshot of whatever last merged. Merge to main asks nothing new; its job is to refresh that baseline so the next PR compares against the right thing. The nightly asks "with all the evidence we can afford, is main still coherent?" — the deep scan a PR gate cannot wait for. The release cut asks "what are we promising, and did we keep the previous promise?" — against the release-contract baseline, the snapshot of the last release, which is the one consumers actually hold.

Two baselines, because they answer two questions: what has been accepted into the tree, and what has been shipped. Baseline Management owns how each is produced, stored, fetched and rotated; this page only places them.

The PR gate

Run the cheap tiers always — the binary and the public headers see every symbol, layout and header-AST break — and add source depth seeded by the translation units the change touched, so the facts that reach neither a binary nor a header AST (a removed public macro, inline function or typedef, a changed template body) are checked where they changed without replaying the whole tree. Without that seeded step those breaks are not covered at all in the PR gate. The report must show breaks and additions: "0 breaks" is not "nothing to review" (Report the Surface, Not Only the Breaks).

abicheck compare baseline.json build/libfoo.so -H include/ --sources new=. \
  --since origin/main --depth source

Pin --depth source explicitly — an unpinned depth stops short of the source tier. The GitHub Action equivalent is mode: compare (the default), with since as a real compare-mode input:

- uses: abicheck/abicheck@v1
  with:
    mode: compare
    old-library: baseline.json  # or abi-baseline: latest-release
    new-library: build/libfoo.so
    new-header: include/
    sources: .
    depth: source
    since: origin/main

What each depth reaches, and the exact command for every other combination, is owned by Evidence Depth § Worked examples; the levels themselves are defined in Evidence & Detectability.

Merge to main

Refresh the accepted-main baseline from the merged tree:

abicheck dump build/libfoo.so -H include/ -o main-baseline.json

When a PR is an intentional break, relax the gate — the intentional-breaking-change label pattern that flips fail-on-breaking off for that PR — and never skip the check: a skipped check leaves the baseline stale, so the break lands on main unrecorded and every later PR fails against it until someone re-baselines by hand. Baseline Management explains the failure in full; the rollout order and the label recipe are in Rollout and Governance.

Nightly

The nightly is where the expensive evidence goes: the unseeded deep scan (--depth source with no --since, which replays the whole target), and the one-build audit (--no-baseline) that lints a single build for accidental exports, private-header leaks and unversioned symbols. This is also where --budget belongs — it fails loudly on overflow rather than shrinking scope — and where --dry-run tells you what a depth would cost before you spend it.

The one-build audit (verified live against catalog/cases/ case145_audit_unversioned_export's snapshot — exit 0, one advisory unversioned_exported_symbol finding):

abicheck compare --no-baseline build/libfoo.so -H include/

--budget is not wired to --no-baseline's one-sided audit path yet (it exits 64 there today) — a nightly deep scan compares against a real baseline anyway, so this is a two-sided compare:

abicheck compare old.so new.so -H include/ --sources . --depth source --budget 15m

Release cut

Publish the release-contract baseline and compare the candidate against the previous release under the release profile, which adds the release recommendation (SONAME or SemVer action) to the report:

abicheck compare last-release.json build/libfoo.so -H include/ --depth source -o markdown=-

The published baseline is what the next release cut compares against, and what consumers can fetch to check themselves.

Cost against confidence

Moment Depth What it catches Cost
Pull request binary + headers, source seeded by the diff every symbol, layout and header-level break; source-only facts in the changed TUs seconds to a few minutes
Merge to main dump only nothing new — keeps the PR gate honest seconds
Nightly unseeded source depth, one-build audit source-only facts anywhere in the tree; hygiene of the build itself the full replay
Release cut source depth, release profile the previous promise, plus the recommendation for the next one the full replay

Numbers are owned by Performance; a scan always states the depth it actually reached (case147), so a report never claims more evidence than it had.

Several libraries or profiles

A product of several libraries, or one library built under several profiles, runs the same four moments per target and folds the results — the declarative topology in .abicheck.yml is the project integration layer, with the independent-targets and monorepo scenarios as the two shapes it takes. Which shape a release of several binaries is, and what one contract across them means, is the subject of Products, Not Libraries.

Now run it

When you are ready to wire a moment into a real project, the tool-track guides carry the exact commands, flags and CI YAML:

You want to… Go to
Pick the right command for your situation (binary compare → full source scan → combine evidence → plugin) Choose Your Workflow
Pin a --depth on compare (or scan for the cases it still owns) Evidence Depth
Produce the source facts — post-build replay, the abicheck-cc wrapper, or the Clang plugin Producing Source Facts
Fold build/source evidence into a baseline snapshot Source & Build Data
Wire a full source scan into GitHub Actionssources/build-info/depth, audit, estimate, cross-check gating GitHub Action: Source Scans
Check a host↔plugin ABI contract Plugin Systems
Gate CI on the right verdict tier (binary break vs. source/API break) CI Gating

Ladder:Assurance Beyond Static Checking · Step 7 · In Practice · Report the Surface, Not Only the Breaks