Exit Codes¶
abicheck uses different exit codes for each command family.
Why they differ: compare is the native interface — 0/2/4 by verdict (or 0/1/2/4 severity-aware), with invalid invocations exiting 64 so a usage error is never mistaken for an ABI verdict. compat mirrors abi-compliance-checker exit codes (0/1/2) so existing ABICC CI scripts work without changes. scan and deps have their own narrower contracts, documented below.
Contract relevance decides what the gate sees (ADR-049)¶
Under --contract-evaluation, each finding's contract relevance is classified
before compatibility policy runs, and policy then scores only the
EVALUATED findings — those whose relevance is IN_CONTRACT or
NOT_APPLICABLE. A PROVEN_OUT_OF_CONTRACT, UNKNOWN_UNPROVEN or
UNKNOWN_UNRESOLVED finding is NOT_EVALUATED: its compatibility_decision
is JSON null and its gate_contribution is 0, so it moves neither the
verdict nor the exit code.
This is a real change to what the compatibility verdict scores, but only in
one direction: since policy now scores the EVALUATED subset of what it used
to score in full, the verdict can only stay the same or get less severe — a
change proven outside the declared contract stops blocking, and a change one
domain cannot resolve stops gating as an ABI break. What it never does is
make either disappear — an excluded finding keeps its ChangeKind, stays in
changes and in the audit ledgers, and is rendered with the relevance and
reason code that say why it did not gate. The other direction — the
overall process exit getting worse — comes from the separate,
independently-orthogonal axis below (missing evidence contributing its own
exit 1), not from relevance itself; that's what stops missing evidence
from being the cheapest way to pass.
Without --contract-evaluation no finding carries a relevance, so every
finding is scored exactly as before and every exit code below is unchanged.
Contract-coverage contribution (ADR-049)¶
compare and scan --against carry an orthogonal contract-coverage axis
under --contract-evaluation. Complete coverage of the mode-selected evidence
domain contributes 0; missing, partial, stale, failed, contradictory, or
identity-incomplete required domain evidence is recorded as a
CoverageFailure in the run-level contract_coverage_failures ledger and
contributes 1. Unrelated provider failures stay advisory. This is a
run-level ledger, not a per-finding field: a coverage gap does not by itself
force every finding to UNKNOWN_UNRESOLVED — an observed root or a
kind that's NOT_APPLICABLE regardless of evidence can still resolve
normally even while the ledger records incomplete evidence elsewhere.
The axis is folded with max, so it raises a clean 0 to 1 and never
lowers a gate's 2/4 — missing coverage cannot demote a real ABI break to
"warnings only". It never rewrites a finding's compatibility decision or its
gate contribution either; it is a floor on the exit status alone. Both
commands fold it identically.
A directory/package compare (the per-library release fan-out) applies the
same flag per library, then maxs every library's own contribution into the
release's exit code — one library's incomplete coverage still raises a clean
release exit 0 to 1, and the release JSON summary states the aggregate in
the same contract_coverage_exit_contribution field. --fail-on-removed-
library's exit 8 is checked ahead of the coverage-only fallback, so a
removed library's own signal is never masked by an unrelated coverage gap
(and a real verdict-based 2/4 still wins outright over both, unchanged).
Without --contract-evaluation there is no selected domain, so the
contribution is always 0 and every other exit code below is unchanged.
Ordinary change suppressions cannot clear a provider/domain coverage failure —
a coverage failure is not a finding, so the suppression machinery structurally
cannot reach one. To accept incomplete contract assurance, set
contract.unresolved: warn (for example via a kind: contract pack). That
zeroes this contribution and changes nothing else: the failures remain listed
in contract_coverage_failures — but only --format json carries that field
at all; markdown, review, HTML, SARIF, and JUnit output surface the same
information as a stderr diagnostic, a SARIF notification, or a JUnit error
suite instead.
Reports state the applied number in contract_coverage_exit_contribution,
which distinguishes contract coverage exit 1 from severity or aggregate
required-target coverage. The composite GitHub Action reads the same field and publishes
verdict: COVERAGE_INCOMPLETE rather than labelling the axis a severity-policy
or operational failure; on compare, where exit 1 is shared, it uses the
report's pre-fold severity.exit_code to tell the two apart. The configured GateDecision independently
contributes 0/1/2/4: a compatible addition can block, and a breaking finding
can be demoted. Only legacy output without a gate block falls back from
compatibility verdict to 2/4. Existing command-specific 5, 8, and 64
behavior is as documented below.
Commands removed in the ADR-043 CLI reset¶
appcompat and plugin-check are gone as standalone commands; their scoping
folded into compare itself — see
Application- and plugin-scoped comparisons
below. baseline (the push/pull/list/delete registry), debian-symbols,
collect, merge, inputs validate, and inputs compact were removed
outright with no CLI replacement — validating a build-emitted
abicheck_inputs/ pack now happens automatically whenever the pack is
consumed, and the debian-symbols/collect/merge library functions remain
available for programmatic (Python API) use only. None of these have their
own exit codes in the current CLI, so they no longer appear in the tables
below.
abicheck compare¶
Legacy exit codes (default, no --severity-* flags)¶
| Exit code | Meaning |
|---|---|
0 |
NO_CHANGE, COMPATIBLE, or COMPATIBLE_WITH_RISK — no binary ABI break |
2 |
API_BREAK — source-level API break — recompilation required |
4 |
BREAKING — binary ABI break |
16 |
not_comparable (ADR-050 D2) — OLD and NEW were not extracted under a comparable profile/scope contract, so no verdict was produced (verdict: null in --format json, with a reason object). Pass --diagnostic-comparison to force a tentative diff instead. |
64 |
Invalid invocation — bad arguments/options or an unreadable/unrecognised input, deliberately outside the 0/2/4 verdict space |
⚠️ Exit
0coversNO_CHANGE,COMPATIBLE, andCOMPATIBLE_WITH_RISK. If your pipeline needs to distinguish them (e.g. warn on deployment risk), use--format jsonand read theverdictfield — exit code alone is not sufficient.
Severity-aware exit codes (with any --severity-* flag)¶
When any --severity-preset or --severity-* option is provided, the exit code
is computed from the severity configuration rather than the verdict:
| Exit code | Meaning |
|---|---|
0 |
No error-level findings |
1 |
Error-level findings in addition or quality_issues only |
2 |
Error-level findings in potential_breaking (but not abi_breaking) |
4 |
Error-level findings in abi_breaking |
16 |
not_comparable (ADR-050 D2) — the comparability gate hard-fails before severity classification ever runs, identical to the legacy scheme's 16. |
The highest applicable code wins. For example, if both abi_breaking=error and
quality_issues=error have findings, the exit code is 4.
ℹ️ The two exit code paths are mutually exclusive. Without
--severity-*flags, the legacy verdict-based path runs. With any--severity-*flag, the severity-aware path runs. They never both execute.
Severity presets¶
| Preset | abi_breaking |
potential_breaking |
quality_issues |
addition |
|---|---|---|---|---|
default |
error | warning | warning | info |
strict |
error | error | error | error |
info-only |
info | info | info | info |
Per-category overrides (--severity-abi-breaking, --severity-potential-breaking,
--severity-quality-issues, --severity-addition) take precedence over the preset.
CI gate patterns¶
# Production gate: fail on any break (legacy exit codes)
abicheck compare old.json new.json
ret=$?
[ $ret -eq 4 ] && echo "BREAKING — release blocked" && exit 1
[ $ret -eq 2 ] && echo "API_BREAK — source-level break" && exit 1
echo "OK (NO_CHANGE or COMPATIBLE)"
# Block unexpected API expansion (severity-aware)
abicheck compare old.json new.json --severity-addition error
ret=$?
[ $ret -eq 1 ] && echo "ADDITIONS — unexpected API expansion" && exit 1
[ $ret -eq 4 ] && echo "BREAKING — release blocked" && exit 1
[ $ret -eq 2 ] && echo "API_BREAK — source-level break" && exit 1
echo "OK"
# Strict mode: all categories at error level
abicheck compare old.json new.json --severity-preset strict
# Permissive gate: fail only on binary breaks
abicheck compare old.json new.json
ret=$?
[ $ret -eq 4 ] && exit 1 # BREAKING only; API_BREAK (exit 2) allowed
exit 0
# Parse exact verdict from JSON (with severity info)
abicheck compare old.json new.json --format json --severity-preset default -o result.json
verdict=$(python3 -c "import json,sys; d=json.load(open('result.json')); print(d['verdict'])" \
|| { echo "ERROR parsing result.json"; exit 1; })
[ "$verdict" = "BREAKING" ] && exit 1
abicheck compare (multi-library / release inputs)¶
When compare is handed directory or package inputs (RPM/deb/tar/conda/wheel),
it fans out to per-library pairs and aggregates the worst per-library verdict
across the release — the behaviour formerly exposed as the standalone
compare-release command (folded into compare per ADR-037 D7; the GitHub
Action's own compare-release/stack-check mode aliases were removed the
same way, per ADR-043 — mode: compare handles directory/package operands
directly). By default a set/release comparison uses the verdict-based scheme
below, plus a dedicated code for removed libraries:
| Exit code | Meaning |
|---|---|
0 |
All libraries compatible (no API/ABI break) |
2 |
Worst verdict is API_BREAK |
4 |
Worst verdict is BREAKING, or an operational ERROR (a library failed to dump/extract/compare) |
8 |
A library was removed between releases and --fail-on-removed-library is set. In the legacy scheme this is emitted only when no API/ABI verdict exit 2/4 and no operational ERROR exit 4 already applies; in the severity-aware scheme it takes precedence over 0/1/2/4. |
16 |
not_comparable (ADR-050 D2) — at least one library's OLD/NEW DSOs were not extracted under a comparable profile/scope contract. Takes precedence over every other outcome in the release, including 8 (removed-library) and a genuine ERROR: a not_comparable result means the comparison couldn't establish what changed at all, so it dominates in both the legacy and severity-aware schemes. Identical code to native compare's own 16. |
On the release path the severity-aware code (0/1/2/4) replaces the
verdict-based 2/4 mapping only when a severity map is actually in effect —
that is, any --severity-* flag is passed or .abicheck.yml carries a
severity: block (a preset or per-category levels). Setting exit_code_scheme:
severity on its own is not enough for directory/package inputs: with no
severity values to apply, the fan-out has nothing to score against and falls
back to the legacy verdict mapping. Under the legacy mapping, an operational ERROR exit 4 or nonzero API/ABI
verdict exit (2/4) wins before the removed-library check; under an effective
severity map, removed-library exit 8 wins over the aggregated 0/1/2/4 code.
An operational ERROR without a higher-priority removed-library result still
floors the severity-aware exit at 4. (--exit-code-scheme is rejected on
directory/package inputs; pin the legacy scheme in config with
exit_code_scheme: legacy if you want to force it.) One consequence worth
gating on: with an effective severity map, a release whose worst verdict is
BREAKING can still exit 0 if that map downgrades ABI breaks (e.g.
abi_breaking: warning) — parse the verdict from JSON output if you need
scheme-independent CI behaviour.
abicheck scan¶
The one-shot source-intelligence scan has its own contract (it may compare
ARTIFACT against --against and adds a budget guard). --against is the
only thing that selects the mode: omit it and scan runs a one-build
audit/hygiene/source-consistency scan only; pass it and scan also compares
ARTIFACT against it — there is no separate --audit flag:
| Exit code | Meaning |
|---|---|
0 |
Compatible (or advisory-only findings) |
2 |
Source-level / API break (incl. API_BREAK cross-source findings) |
4 |
ABI break (from the --against comparison) |
5 |
--budget overflow — the time guard tripped (scope is never silently shrunk) |
6 |
NOT_COMPARABLE (ADR-050 D2) — ARTIFACT and --against were not extracted under a comparable profile/scope contract, so the comparison never ran (diff.reason in --format json). Distinct from compat check's 9 and native compare's 16 — every command maintains an independent exit-code scheme. |
64 |
Invalid invocation (bad arguments/options) |
Exit
5is unique toscan:--budget 15mfails the run rather than quietly dropping evidence. Use--dry-runto preview the audit checks and (if--againstis given) the comparison that would run, plus the projected per-layer cost, without scanning — like every command's--dry-runit only ever exits0/1/64, never a verdict code; see--dry-runbelow.
abicheck aggregate¶
The multi-target fan-in gate folds the per-target compare/scan JSON reports
a CI build matrix produces (one abi-report-<target>.json per leg) into one
gate decision. Four axes stay orthogonal (ADR-042, extended by ADR-049
Phase 7), and the exit code is the worst contribution across them:
- gate — each report already carries its own severity gate decision
(
severity.{exit_code,blocking,blocking_categories});aggregatecombines those, it never recomputes a gate from the compatibility verdict. So aCOMPATIBLEreport with anaddition=errorpolicy still contributes exit1, and aBREAKINGreport under a demoted preset can contribute0. Ascanreport is read via its own top-levelexit_code(keyed onscan_schema_version). Reports produced without any gate block fall back to the legacy verdict→exit mapping (0/2/4). Reading is fail-closed: a report whose gate block is present but corrupt (an out-of-range or non-integerexit_code, ablockingflag that contradicts it, non-string categories) makes that target unavailable — never silently reverting to the greener legacy path. - coverage — did every required expected target actually report? An
incomplete required coverage is a coverage failure at exit
1; it is never promoted to an ABI-break exit4. This is a different question from contract coverage below: this one asks whether the matrix ran and reported at all. - compatibility — the worst verdict over the analyzed targets, reported for context; it does not by itself drive the exit code.
- contract_coverage — reads back each already-analyzed target's own
contract_coverage_exit_contribution(per-report field; see "Contract-coverage contribution" above) and folds it withmax, same as the other axes — added to the aggregate schema alongside this axis (abicheck.aggregate.AGGREGATE_SCHEMA_VERSIONis the versioned fact owner);aggregatenever recomputes it. This is a different question again from plaincoverage: a required target can have reported successfully (no coverage gap) while its own evidence for the selected contract domain was still incomplete (a contract-coverage gap). Both can independently produce exit1, for unrelated reasons, andaggregaterecords which one fired rather than merging them into one undifferentiated1.
| Exit code | Meaning |
|---|---|
0 |
Every required target analyzed, no blocking findings |
1 |
A required target was unavailable (coverage gap, default --on-missing-required fail); an analyzed target's gate blocks on an addition/quality finding only; a target's own contract-coverage evidence was incomplete under --contract-evaluation; or a non-verdict per-report failure folds here (e.g. a scan report's budget-overflow exit 5) |
2 |
An analyzed target's gate is a source-level / API break |
4 |
An analyzed target's gate is an ABI break |
64 |
Invalid invocation (bad arguments/options, malformed manifest, duplicate target id, or no expected-target set given) |
The highest applicable code wins: a run with both an ABI break and a coverage
gap exits 4; a run whose only problem is a missing required target exits
1, never 4.
A required target with no report is unavailable (unknown), never counted as compatible. This is the whole point of the command: a matrix leg that failed before uploading its report is reported as a coverage gap and fails the gate at exit
1— it is never silently folded into the verdict as an empty, compatible ABI, and a build that simply never ran is never handed an ABI-break exit4.
Declaring the expected-target set (required — one of):
--manifest abi-targets.json(recommended) — the single source of truth for which targets the matrix must produce:{"targets": [{"id": "linux-x86_64", "required": true}, ...]}. Generate it once in the plan job and feed the same file to both the matrix and this gate so they never drift.--run-plan run-plan.json— a project run-plan fromabicheck project plan, projected to the same expected-target shape internally (recommended for aproject plan-driven workflow instead of a separate manifest projection step); each check's owncheck_idbecomes the expected target id, matching whatcheck-targetwrites as every report'starget_id.--expect <ids>(repeatable / comma-separated), with optional--optional <ids>— an inline alternative to a manifest file.--discovered-only— explicitly aggregate whatever reports are present with no required-target coverage gate (a missing target is simply not counted, never a coverage failure — thecontract_coverageaxis is unaffected and still applies to whatever is present). Required to run without a manifest/--expect: with no declared target set the gate cannot tell a missing required target from an intentionally absent one, so a bareaggregate reports/is a usage error (exit64), not a silent pass.
--on-missing-required warn downgrades a coverage gap to advisory (the
per-target gate decisions alone then decide the exit code). --on-unexpected-target
(include/warn/fail/ignore, default include) controls a report whose
target is not in the expected set: include counts its real findings in the
gate but not in required coverage. The --format json output is versioned
(aggregate_schema_version — see abicheck.aggregate.AGGREGATE_SCHEMA_VERSION
for the current value) and carries the four axes
separately under gate / coverage / compatibility / contract_coverage
— the last is {"exit_contribution": 0, "incomplete_targets": []}-shaped and
present even when no target used --contract-evaluation (an empty
incomplete_targets list, not an omitted block).
When targets are checked under several toolchain profiles (report ids of the
form target@profile#channel@depth), two additional reporting-only blocks
group them back together: profile_matrix (one entry per logical target,
with affected_profiles/verdict_by_profile) and finding_matrix (one
entry per distinct finding, with affected_profiles /
unaffected_profiles / undetermined_profiles and a scope of
all_profiles / profile_specific / partial / undetermined). A profile
whose report is missing, unreadable, not-comparable, or carries no changes
array is undetermined — never reported as clean of a finding it was never
checked for. Neither block affects the exit code; the full field list is in
aggregate_report.schema.json.
Application- and plugin-scoped comparisons (compare --used-by/--required-symbol)¶
The standalone appcompat and plugin-check commands are gone (ADR-043).
Their scoping now folds into compare itself:
compare --used-by APP(repeatable) — foldsappcompat.APPis a real application binary; its actual imports/required symbol versions scope the comparison.OLD/NEWmay be real library binaries or JSON snapshots that carry binary evidence (adumpof a real library, not headers-only). Mutually exclusive with--required-symbol/--required-symbols.compare --required-symbol SYM(repeatable) /--required-symbols FILE— foldsplugin-check. Scopes the comparison to an explicit dlopen/dlsym entrypoint contract instead of the full diff. Mutually exclusive with--used-by.
The full library comparison still runs once; the worst app/plugin-scoped
result becomes the primary verdict/exit code, with the full verdict and
unrelated changes kept as informational context. There is no separate
exit-code scheme for this scoping — it uses exactly the compare codes
documented above (legacy 0/2/4, severity-aware 0/1/2/4, 64 for a usage
error). In particular, exit 4/BREAKING is also the result when the
application requires symbols or ELF version tags absent from the new
library — even if the unscoped library diff is otherwise compatible —
because the application would fail to load.
abicheck deps tree¶
| Exit code | Meaning |
|---|---|
0 |
All dependencies resolved, all required symbols bound |
1 |
Missing dependencies or unresolved symbols (binary would fail to load) |
64 |
Invalid invocation (bad arguments/options) |
--dry-run shows the resolved binary path and search order without
resolving the dependency tree — see
--dry-run below.
abicheck deps compare¶
Sysroot flags are --old-root/--new-root (default / for each —
renamed from the old --baseline/--candidate).
| Exit code | Verdict | Meaning |
|---|---|---|
0 |
PASS |
Binary loads and no harmful ABI changes |
1 |
WARN |
Binary loads but ABI risk detected in dependencies |
4 |
FAIL |
Load failure or binary ABI break in dependencies |
5 |
— | not_comparable (ADR-050 D2) — at least one dependency's before/after DSOs were not extracted under a comparable profile/scope contract, so its per-library ABI diff never ran. Dominates 0/1/4, the same "couldn't establish what changed" precedence the gate uses elsewhere. |
64 |
— | Invalid invocation (bad arguments/options) |
--dry-run shows the old/new roots, resolved binary paths, and search order
without running per-library ABI diffs — see
--dry-run below.
CI gate patterns¶
# Full-stack check: fail on FAIL, warn on WARN
abicheck deps compare usr/bin/myapp --old-root /old-root --new-root /new-root
ret=$?
[ $ret -eq 4 ] && echo "FAIL — load failure or ABI break" && exit 1
[ $ret -eq 1 ] && echo "WARN — ABI risk detected" && exit 1
[ $ret -ne 0 ] && echo "ERROR — unexpected non-verdict exit code: $ret" && exit 1
echo "PASS"
# Permissive: only fail on load failure / ABI break
abicheck deps compare usr/bin/myapp --old-root /old-root --new-root /new-root
ret=$?
[ $ret -eq 4 ] && exit 1 # FAIL only; WARN (exit 1) treated as OK
[ $ret -ne 0 ] && [ $ret -ne 1 ] && exit 1 # fail closed on non-verdict errors
exit 0
abicheck compat¶
Matches abi-compliance-checker exit codes (ABICC drop-in):
| Exit code | Meaning |
|---|---|
0 |
No breaking changes (NO_CHANGE or COMPATIBLE) |
1 |
BREAKING (mirrors ABICC) |
2 |
API_BREAK (source-level break; non-verdict failures use extended codes below) |
Non-verdict/tool failures are classified via Extended compat error codes (ABICC-style) below (
3,4,5,6,7,8,9,10,11).
Extended compat error codes (ABICC-style)¶
In abicheck compat, non-verdict failures are further classified where possible:
| Exit code | Typical cause |
|---|---|
3 |
Required external command/tool is missing (for example castxml) |
4 |
Cannot access input files (missing or permission denied) |
5 |
Header compile/parsing failure during dump |
6 |
Invalid compat configuration/input (descriptor, suppression, regex flags) |
7 |
Failed to write report/output artifact |
8 |
Dump/analysis pipeline failure |
9 |
not_comparable (ADR-050 D2) — OLD and NEW were not extracted under a comparable profile/scope contract, so no verdict was produced. Distinct from native compare's own 16 — the two commands maintain independent exit-code schemes. |
10 |
Generic internal/tool failure fallback |
11 |
Interrupted run |
Note: classification is best-effort and context-dependent;
API_BREAKremains2.
--dry-run (dump, compare, scan, deps tree, deps compare)¶
Every one of these five commands accepts --dry-run: it resolves and
validates the invocation — classifies inputs, discovers config, and (per
command) shows which data layers (L0–L5) are available, the audit checks and
comparison that would run, or the resolved binary path/search order — and
prints a report without doing the real work. It is cheap and read-only:
no compiler invocation, no build-system query, no network access, and it
writes nothing — passing -o/--output together with --dry-run is a
usage error.
| Exit code | Meaning |
|---|---|
0 |
Resolved cleanly — ok to proceed |
1 |
Blocked — the invocation would fail once actually run |
64 |
Usage error (e.g. -o/--output passed together with --dry-run) |
--dry-run never returns a verdict code. It exits 0/1/64 only —
never 2, 4, 5, or 8, even on a command whose real run could produce
one of those.
Summary table¶
| Verdict / State | compare exit (legacy) |
compare exit (severity) |
scan exit |
deps tree exit |
deps compare exit |
compat exit |
|---|---|---|---|---|---|---|
NO_CHANGE / PASS / compatible |
0 |
0 |
0 |
0 |
0 |
0 |
COMPATIBLE |
0 |
0 |
0‡ |
— | — | 0 |
COMPATIBLE_WITH_RISK |
0 |
0–2* |
0‡ |
— | — | 0 |
| Additions only | 0 |
0–1* |
0‡ |
— | — | n/a |
| Quality issues only | 0 |
0–1* |
0‡ |
— | — | n/a |
WARN (ABI risk) |
— | — | — | — | 1 |
— |
API_BREAK |
2 |
0–2* |
2 |
— | — | 2 |
BREAKING / FAIL |
4 |
4 |
4 |
— | 4 |
1 |
--budget overflow |
— | — | 5 |
— | — | — |
| Missing dependencies/symbols | — | — | — | 1 |
— | — |
| Load failure | — | — | — | — | 4 |
— |
| Invalid invocation / tool error | 64† |
64† |
64† |
64† |
64† |
3/4/5/6/7/8/10/11 |
App/plugin-scoped comparisons (compare --used-by/--required-symbol) reuse
the compare columns above — see
Application- and plugin-scoped comparisons.
aggregate combines each report's own severity gate (0/1/2/4) over its
analyzed targets and adds a coverage gate (a required gap exits 1, never 4) —
see abicheck aggregate.
--dry-run (on dump/compare/scan/deps tree/deps compare) reuses
none of these rows — it always exits 0/1/64; see
--dry-run above.
* Severity exit codes depend on the configuration. For example, with
--severity-addition error, additions exit 1; with --severity-preset
info-only, everything exits 0.
† Every command exits 64 for an invalid invocation — bad arguments/options
or an unreadable/unrecognised input — deliberately outside the verdict/result
space so a usage error is never mistaken for a compatibility result. To
reliably distinguish verdicts from errors in a script, use --format json and
read the verdict field where available.
‡ scan's own scheme collapses every compatible/advisory-only state (no
break, deployment risk, additions, quality signals) to exit 0 — read
--format json if your pipeline needs to distinguish them.
Strict mode (-s / -strict)¶
compat (and only compat) supports strict mode to promote lesser verdicts:
# Strict mode: COMPATIBLE + API_BREAK → exit 1 (BREAKING)
abicheck compat -lib foo -old OLD.xml -new NEW.xml -s
# Strict API-only: only API_BREAK → exit 1; COMPATIBLE stays exit 0
abicheck compat -lib foo -old OLD.xml -new NEW.xml -s --strict-mode api
--strict-mode values:
- full (default when -s is set): COMPATIBLE + API_BREAK → BREAKING
- api: only API_BREAK → BREAKING; COMPATIBLE unchanged
--strict-mode has no effect unless -s is also passed.
Note:
abicheck comparedoes not have-s/--strictflags. For compare-mode strict pipelines, use CI exit code logic (check exit2as a failure).