abicheck/abicheck — ABI Compatibility Checker

ABI/API compatibility of C/C++ binaries, packages, and releases in CI: what changed, what breaks, what was not checked.

View on GitHub

Trust Signals

Scorecard Score
not yet scored
Maintenance Recency
Activelast commit Jul 10, 2026
License
Apache 2.0

Pinned Snippet

workflow.ymlSHA-pinned
uses: abicheck/abicheck@7bbc3ca44d7548bb52c73ef6af6b2476ce51549b # v0.4.0

tags can be moved; commit SHAs can't. why a SHA?

namedescriptionrequireddefault
modeOperation mode. The Action exposes five analysis modes, mirroring abicheck's five core per-library CLI commands (compare/dump/scan/ deps tree/deps compare — ADR-043; the CLI additionally has project-orchestration commands like `aggregate` and `project plan` (ADR-054) that this Action does not expose as separate modes): "compare" (default) compares two ABI surfaces — old-library/new-library may be single binaries/snapshots OR directories/packages, in which case abicheck fans out to a per-library comparison automatically (no separate mode); "dump" generates a JSON baseline snapshot from a single library (now also embeds optional L3/L4/L5 build-source evidence via source/build inputs); "scan" runs the deterministic source-intelligence orchestrator (classify → always-on pattern/cross-source tier → pinned evidence level, optionally compared against a baseline) — the entry point for source scans; "deps-tree" resolves transitive dependencies and symbol bindings for one binary (Linux ELF); "deps-compare" compares a binary's full dependency stack across two sysroots (Linux ELF). nocompare
old-libraryPath to the old library, JSON snapshot, or ABICC Perl dump. Required when mode=compare. May also be a directory or package (RPM, Deb, tar, conda, wheel) — abicheck then fans out to a per-library comparison automatically. no
new-libraryPath to the new (current) library, binary, or JSON snapshot. Required for compare, dump (unless a source-only sources/build-info/compile-db input is given), scan, and deps-tree/deps-compare modes. May be a directory or package (RPM, Deb, tar, conda, wheel) ONLY in compare mode, matching old-library — compare then fans out to a per-library comparison automatically. dump and scan each analyse exactly one artifact and reject a directory/package with an error (they have no per-library fan-out); for a multi-library release, dump/scan each library individually or use compare instead. no
new-library-setscan mode only (ADR-056). Audit a *set* of libraries with no old side, as one operation: a directory (every discoverable shared library in it) or an explicit comma-separated path list. Maps to scan --artifact-set. Mutually exclusive with new-library and with against/abi-baseline (audit-only — no old-side comparison for a set). For a multi-library release comparison against an old side, use mode: compare with a directory/package operand instead. no
debug-info1Debug info package for old side (RPM/Deb/tar). compare mode, directory/package operands only.no
debug-info2Debug info package for new side (RPM/Deb/tar). compare mode, directory/package operands only.no
devel-pkg1Development package with headers for old side. compare mode, directory/package operands only.no
devel-pkg2Development package with headers for new side. compare mode, directory/package operands only.no
dso-onlyOnly compare shared objects, skip executables. compare mode, directory/package operands only. Deliberately no declared default (Codex review, fresh evidence): the synthesized release:/gate: config overlay needs to tell "omitted" apart from an explicit "false" (which must override a discovered/explicit .abicheck.yml's own release.dso_only: true) -- a declared default would make every omitted invocation indistinguishable from an explicit false, silently losing that override.no
include-private-dsoInclude private (non-public) shared objects from non-standard paths. compare mode, directory/package operands only. Deliberately no declared default -- see dso-only's own description for why.no
keep-extractedKeep extracted temporary files for debugging. compare mode, directory/package operands only.nofalse
fail-on-removed-libraryExit 8 when a library present in old is proven removed in new -- NEW must have a proven-complete declared inventory: either a stored ProjectSnapshot package whose capture asserted inventory_complete, or a package archive (e.g. .rpm/.deb) whose own readable member table this run extracted and could enumerate in full (ADR-065 D2/S3); a live directory, or an archive whose member table could not be read in full, is an unproven inventory and an unmatched library there is an incomplete scope instead, governed by the scope.on_incomplete key in .abicheck.yml. compare mode, directory/package operands only. Deliberately no declared default -- see dso-only's own description for why.no
headerPublic header file(s) applied to both sides (space-separated). Required when input is an ELF binary; ignored for JSON snapshots. If old and new actually declare different header sets (e.g. a new release added a header), set `old-header`/`new-header` instead -- a bare `header` parses BOTH snapshots against the same files, which silently hides the removed/added declarations you're trying to check. no
old-headerPublic header(s) for the old side only (overrides header for old). Space-separated.no
new-headerPublic header(s) for the new side only (overrides header for new). Space-separated.no
public-header-dirDirectory whose headers are treated as public for provenance classification (repeatable, space-separated) — establishes the public/internal boundary so leakage/RTTI/exported-vs-public cross-checks run instead of skipping. dump mode forwards it as one more `-H/--header` root, which is where `dump` reads declaration provenance from AND extracts headers from (a directory entry tags everything under it public and expands to every header inside). scan mode forwards it both as `--public-header-dir` (scope only) and as an `-H` root, so its own header extraction expands the same directory `dump` does — keeping a `scan --against` a fresh `dump` baseline of the same inputs comparable instead of drifting apart on `include_sequence` for no real recipe difference (lab report). The `-H` root is candidate-sided (`new=...`) for a scalar scan with a resolved baseline — so it never leaks into the baseline/old side's own header parse even when `old-header` is also given — and bare (both-sided) for an audit-only or `--artifact-set` scan, which has no old side to protect. compare has no equivalent flag. no
build-targetExplicit build-system root target(s) to scope L3 evidence collection to, instead of a workspace-wide query (P0.2; Bazel only so far, e.g. `//:math`). Space-separated, repeatable — each root's transitive dependency closure is unioned. Forwarded identically to both dump and scan mode (`--build-target`, same flag and semantics on both); CLI equivalent of `.abicheck.yml`'s `build.targets`, overrides it when both are given. Without this, a multi-package workspace with fixture/test targets alongside the real library is collected in full, which can pollute L3 evidence with unrelated compile units and diverge a `scan --against` a target-scoped `dump` baseline's own evidence (lab report). compare has no equivalent flag. no
includeExtra include directories for header AST parsing (castxml or clang). Space-separated. Set this (or `old-include`/`new-include` for a side-specific root) whenever a header passed via `header` itself `#include`s a *dependency's* header from a separate include root (e.g. PVXS's own `version.h` pulling in EPICS Base's `epicsVersion.h`, found in a real PVXS Action acceptance run) -- `header`/`public-header-dir` alone never cover a dependency's own headers. On ELF the parse then aborts on the first such include with a "file not found" error; on Mach-O/PE it can instead warn and fall back to export-table scoping (`--header`/`--include` are silently ignored for that dump), which is quieter but leaves header-based coverage incomplete rather than failing loud. In `compare` mode, `old-include`/`new-include` *replace* this list for their side rather than adding to it (repeat any shared directory there too); in `dump`/`scan`, a side-specific include is added alongside this list instead. (`sources`/`build-info` can sometimes auto-derive a build's own include dirs for this instead of a manual `-I`, when neither side has any explicit include input of its own.) no
old-includeInclude directories for old side only. Space-separated. Used by `compare` and by `scan --against` (the baseline side); `dump` and scan's audit-only `new-library-set` mode have no old side. In `compare`, replaces `include` for this side rather than adding to it (repeat any directory `include` also lists that's still needed); in `scan --against`, it is added alongside `include` instead. no
new-includeInclude directories for new side only. Space-separated. In `compare` mode, replaces `include` for this side rather than adding to it (repeat any directory `include` also lists that the new side still needs); in `dump`/`scan`, it is added alongside `include` instead. no
old-versionVersion label for the old library (embedded in reports).noold
new-versionVersion label for the new library (embedded in reports).nonew
langLanguage mode for header AST parsing: "c++" or "c".noc++
ast-frontendL2 header-AST frontend for native-binary inputs: "auto" (resolves to castxml and never changes producer unless the CLI's --allow-ast-frontend-fallback flag is also passed via extra-args (or ABICHECK_ALLOW_AST_FALLBACK=1 is set) — with that opt-in, a recognized castxml toolchain-version mismatch, an unsupported castxml release, or a direct-include-guard failure falls back to clang, G16; without it, and on any host with no castxml at all, auto does not silently switch to clang — the command fails asking you to install castxml or pass ast-frontend: clang explicitly. One exception: a non-host --frontend-context (SYCL/DPC++, passed via extra-args) routes an auto that resolves to plain castxml (no pin) straight to clang with no opt-in needed, since castxml can't satisfy it at all — but any way of pinning the resolved backend away from plain castxml-or-clang still rejects it: an explicit ast-frontend: castxml, auto pinned to castxml via ABICHECK_AST_FRONTEND=castxml, and hybrid (explicit or auto pinned via ABICHECK_AST_FRONTEND=hybrid — hybrid has no device concept either) all reject a non-host context. Explicit ast-frontend: clang satisfies it directly, and so does auto pinned to clang via ABICHECK_AST_FRONTEND=clang), "castxml" (default), "clang" (clang -ast-dump=json; explicit opt-in for clang-only hosts), or "hybrid" (runs both and merges them; needs both tools installed on the runner, never auto-selected). Applies to dump, scan, and compare mode with a single-pair (non-directory, non-package) operand — the per-library fan-out a directory/package compare uses never threads this L2 context to each pair's header dump, so a value other than "auto" (the no-op default) is a hard error there (shared `compile_context_options`, ADR-037 D3). Same as the ABICHECK_AST_FRONTEND env var. no
gcc-pathPath to GCC/G++ (or clang) cross-compiler binary. On scan, maps to the CLI's --compiler; on dump and single-pair compare, Phase 7 removed that flag from the CLI entirely, so this Action folds it (together with gcc-prefix, gcc-options, sysroot, and nostdinc, when any of those are also set) into a synthesized .abicheck.yml compile: block forwarded via --config instead. Applies to dump, scan, and compare mode with a single-pair (non-directory, non-package) operand — a directory/package compare rejects it with an error (same restriction as ast-frontend above). Combining any of this group with build-config is supported: the synthesized compile: block is merged into a copy of the named build-config, and this Action's input wins on a key conflict. no
gcc-prefixCross-toolchain prefix, e.g. aarch64-linux-gnu-. On scan, maps to the CLI's --compiler-prefix; on dump and single-pair compare, it folds into the same synthesized compile: block gcc-path describes above (a full gcc-path wins if both are set, since the merged compile.compiler config key can only hold one). Applies to dump, scan, and compare mode with a single-pair (non-directory, non-package) operand — a directory/package compare rejects it with an error (same restriction as ast-frontend above). no
gcc-optionsExtra compiler flags passed through to the header frontend. On scan, maps to the CLI's --compiler-option; on dump and single-pair compare, it folds into a synthesized compile.options list in the same synthesized compile: block gcc-path describes above. A single-line value is shell-quoting-aware split on whitespace into separate flags (a quoted segment such as -DMSG="hello world" stays one flag); a multi-line (YAML block scalar) value treats each line as one already- complete flag, not split further. On scan, that per-line flag is forwarded verbatim as one --compiler-option occurrence even if it contains a space; on dump and single-pair compare, each synthesized compile.options entry must be a single whitespace-free flag, so a line containing a space is rejected with a clear error rather than silently accepted — put each word needing its own flag on its own line instead. Applies to dump, scan, and compare mode with a single-pair (non-directory, non-package) operand — a directory/ package compare rejects it with an error (same restriction as ast-frontend above). no
sysrootAlternative system root directory for cross-compilation. On dump and single-pair compare this folds into the same synthesized compile: block gcc-path describes above (scan and deps-tree keep their own direct --sysroot/CLI forwarding). Applies to dump, scan, deps-tree, and compare mode with a single-pair (non-directory, non-package) operand — a directory/package compare rejects it with an error (same restriction as ast-frontend above).no
nostdincDo not search standard system include paths. On dump and single-pair compare this folds into the same synthesized compile: block gcc-path describes above. Applies to dump, scan, and compare mode with a single-pair (non-directory, non-package) operand — a directory/package compare rejects it with an error (same restriction as ast-frontend above). nofalse
follow-depsInclude transitive dependency graph and symbol binding info in dump or compare output. ELF only. Applies to dump and compare modes. nofalse
old-rootSysroot for the old (baseline) environment. Required for mode=deps-compare. no
new-rootSysroot for the new (candidate) environment. Required for mode=deps-compare. no
search-pathAdditional directories to search for shared libraries (space-separated). Used by deps-tree, deps-compare, and follow-deps. no
ld-library-pathSimulated LD_LIBRARY_PATH (colon-separated). Used by deps-tree, deps-compare, and follow-deps. no
used-byApplication binary/binaries whose actual imports/required symbol versions scope the comparison (space-separated; maps to repeated compare --used-by). The full library comparison always determines this run's own verdict/exit code; the supplied application's own confirmed/potential/unresolved impact is reported alongside it (informational), never in place of it. Mutually exclusive with required-symbol/required-symbols (the CLI rejects both being set). compare mode only. no
used-by-manifestPath(s) to a JSON document naming one or more consumer binaries (space-separated; maps to repeated compare --used-by-manifest), each with optional digest/platform/profile/provider_baseline provenance and a requirement ("required", the default -- an unreadable consumer aborts the run, same as used-by -- or "advisory" -- an unreadable consumer is skipped and reported instead). Merged into the same scoping pipeline as used-by; every listed consumer counts toward the reported "N of M consumers affected" summary (consumer_impact_summary in --format json). Mutually exclusive with required-symbol/required-symbols. compare mode only. no
required-symbolAn exported linker symbol a plugin host resolves via dlopen/dlsym and requires (space-separated; maps to repeated compare --required-symbol). The full library comparison always determines this run's own verdict/exit code; this contract's own confirmed/ potential/unresolved impact is reported alongside it (informational), never in place of it. Mutually exclusive with used-by. compare mode only. no
required-symbolsPath to a file of required symbols, one per line (blank lines and '#' comments ignored) -- combined with any required-symbol values. compare mode only. no
againstscan mode only. Baseline artifact (a library binary or JSON snapshot) to compare the scanned new-library against. Maps to scan --against; omit for a one-build audit (see the audit input below). Overridden by abi-baseline when set. no
sourcesSource checkout / tree for source-intelligence analysis. The compile database is auto-discovered within it. Drives L4 source-ABI replay and L5 source-graph collection. Used by scan and dump modes, and by compare mode for the new (candidate) side only — mapped to compare's --sources new=... (the old side's evidence, if any, is expected to already be embedded in whatever old-library snapshot was resolved). no
build-infoOut-of-tree L3 build context: a build directory, a compile_commands.json, or a previously-collected evidence pack. Use when the build tree lives outside --sources. Used by scan and dump modes, and by compare mode for the new (candidate) side only — mapped to compare's --build-info new=... (same old-side caveat as sources above). no
compile-dbExplicit path to a compile_commands.json, when it is not under `sources`. Folded into `--build-info` for every mode (scan, dump, and compare mode's new side) — `--build-info` already accepts a build dir, a compile_commands.json, or a pre-captured pack, so it is the one flag that takes this operand. no
build-configPath to a trusted .abicheck.yml. For scan mode, supplying this together with an explicitly pinned depth is what permits its build.query (compile-DB generation) command to run; a plain scan with no depth pinned does not auto-run it. For dump mode, supplying this alone is enough — an explicit --config is itself the operator's consent to run its build.query. Used by scan and dump modes, and by compare mode (--config). Also the only way to configure the cross-library bundle-analysis layer's system-provider allow-list extension and co-versioned cohort declarations: .abicheck.yml's `bundle:` block (`system_providers:`/`cohorts:`, CLI cleanup phase two, PR J — there is no longer a per-invocation Action input for either, matching the native CLI's own removal of --bundle-system-providers/ --bundle-cohort). `system_providers:` applies to compare mode (directory/package operands) and scan mode (--artifact-set) alike; `cohorts:` (the SONAME-skew check) applies to compare only — an --artifact-set audit has no old/new release pair to detect a skew between, so the setting has no effect there. no
jobsRemoved and ignored (ADR-068 D5). This input used to forward `-j/--jobs` to `compare`, capping the directory/package release fan-out's worker count. Both the flag and the input are gone: the release engine auto-detects the CPU count and clamps it to available memory, and there is no manual override any more. Kept registered as a tombstone — deleting the input outright made GitHub drop it before the Action ever ran, so a pinned workflow that still sets it got no error and no annotation, and its worker cap silently stopped applying. Setting it now emits an explicit warning telling you it has no effect. Remove it from your workflow. no
bundle-system-providersRemoved (CLI cleanup phase two, PR J). The cross-library bundle-analysis layer's system-provider allow-list extension moved to `.abicheck.yml`'s `bundle.system_providers:` block, reachable through the `build-config` input. Kept registered as a tombstone for the same reason as `jobs` above — an undeclared input is dropped silently, which here would drop a real analysis setting. Setting it is a hard error: move the list into `.abicheck.yml` and point `build-config` at it. no
allow-build-queryDeprecated and ignored. The `--allow-build-query` dump flag it used to forward was already a no-op and has since been removed outright (CLI cleanup H1); this input is kept registered only so an existing workflow that still sets it doesn't get an unknown-input warning. abicheck always runs its own inferred, abicheck-authored cmake/bazel/make compile-DB query whenever a sources input needs build context: pointing abicheck at a source tree is itself the request to analyse it. A trusted, operator-supplied build.query (`build-config`) needs no opt-in either — see `build-config` above. nofalse
depthEvidence-depth selector: binary, headers, build, or source. Used by scan and dump modes, and by compare mode (--depth). In scan mode, omit it for 'auto' (risk-driven); --depth source analyses the whole current library target unless since/changed-path seeds a narrower changed scope. Maps to --depth. (The deprecated scan-mode/source-method inputs have been removed; use depth.) no
sincecompare mode (single-pair operands) and scan mode. Focus the run's source-evidence scope on files changed vs a git ref (e.g. origin/main). Maps to compare --since / scan --since. Not forwarded for a compare directory/package operand (the per-library release fan-out collects no build/source evidence for either side to scope). no
changed-pathcompare mode (single-pair operands) and scan mode. Changed path(s) to focus the run's source-evidence scope on (space-separated; alternative to since). Maps to repeated compare --changed-path / scan --changed-path. Not forwarded for a compare directory/package operand, same as since above. no
budgetscan mode only. Time guard (e.g. 15m, 900s, 1h). The step FAILS on overflow (exit 5) — a budget never silently shrinks scope. Maps to scan --budget. no
dry-runResolve inputs/config and print what the run would do, without performing any analysis or writing output. Exits 0 for a resolvable preview; an invocation the real run would itself reject (an invalid flag combination) still exits with that same usage error, and a requested-but-unsatisfiable depth/evidence contract still exits nonzero — a dry run validates what it can see, it does not turn every outcome into success. The one deliberate exception is this composite Action's own baseline resolution: an unresolved abi-baseline is tolerated under dry-run rather than hard-failing, since the point of a preview is to report what *would* be compared, not to require the comparison already be resolvable. Maps to --dry-run; supported by every mode (compare, dump, scan, deps-tree, deps-compare). For scan, this also reports the projected per-layer cost (the same preview the deprecated estimate input below used to provide). To force a scan-mode single-build hygiene lint instead of a baseline compare, simply omit against/abi-baseline for that step — scan already runs audit-only whenever no against is given (the deprecated audit input below is a functional alias for this). nofalse
estimateDeprecated alias for dry-run (scan mode only, historical name). Setting this to true is equivalent to dry-run: 'true'. Prefer dry-run directly, which now applies to every mode, not just scan. Kept as a functional (not just accepted-and-ignored) alias for backward compatibility with existing workflows. nofalse
auditDeprecated. scan mode only. Forces a one-build audit-only run (omits --against) even when against/abi-baseline is configured elsewhere in the workflow. Prefer simply omitting against/abi-baseline for the step instead — scan already runs audit-only whenever no against is given. Kept as a functional (not just accepted-and-ignored) alias for backward compatibility with existing workflows. nofalse
crosscheckscan mode only. Per-check severity overrides KEY=LEVEL (off|info|warning|error), space-separated. Maps to repeated scan --crosscheck. Promote a check to =error to gate CI on it. no
risk-rulesscan mode only. Path to a YAML file overriding the risk_rules profile. Maps to scan --risk-rules. no
abi-baselineAutomatically fetch an ABI baseline snapshot for compare mode (used as old-library) or scan mode (used as the scan baseline). Values: "latest-release" fetches *.abicheck.json from the latest GitHub Release; a tag name (e.g. "v2.0.0") fetches from that release; a file path uses the file directly. Requires GITHUB_TOKEN with contents:read permission. Overrides old-library (compare) / against (scan) when set. When the resolved release has no single `*.abicheck.json[.gz|.zst]` asset, set baseline-profile and baseline-target to instead fetch a release-contract baseline-set archive (`abicheck-baseline-<profile>.tar.zst`, published by publish-baseline.yml -- see docs/reference/publish-baseline.md); the single-snapshot search always takes priority when both exist. no
baseline-profileSelects which contract profile's baseline-set to fetch when abi-baseline resolves to a release-contract baseline-set archive (docs/reference/publish-baseline.md) rather than a single *.abicheck.json asset. Requires baseline-target to also be set. Omit when the release publishes a single-snapshot asset (abi-baseline's original, unchanged contract) -- this input is only consulted as a fallback when that search finds nothing. no
baseline-targetThe target/library id (the baseline-set manifest.json's "library" field, matching the name passed to actions/baseline's `libraries` input when the baseline-set was published) to resolve from the baseline-set archive selected by baseline-profile. Required when baseline-profile is set. no
baseline-asset-name-templateRelease asset filename template for the baseline-set archive fetched via baseline-profile; "{profile}" is replaced with baseline-profile's value, and "{generation}" (optional) with baseline-generation's value. Must match the asset-name-template the publishing workflow (publish-baseline.yml, or a custom equivalent) used. noabicheck-baseline-{profile}.tar.zst
baseline-generationSubstituted for "{generation}" in baseline-asset-name-template -- set this when the publishing workflow's asset-name-template includes "{generation}" (e.g. it published a baseline scoped to a scanner-compatibility generation -- see baseline-management.md's "Scanner upgrades and baseline generations" section). Omit when baseline-asset-name-template doesn't reference "{generation}" at all -- the default template never does, so this input is a no-op unless you opt in by including the placeholder. no
formatOutput format: json, sarif, html, or markdown/text (each mode's default when this input is left unset — markdown for compare/ deps-tree/deps-compare, text for scan). sarif is only supported in compare mode with a single-pair (non-directory, non-package) operand — a directory/package compare rejects it with an error. html is supported by compare (same single-pair restriction as sarif) and by deps-tree/deps-compare (a dependency-stack report). scan supports text (its default) and json only. dump ignores this input; it always writes a JSON snapshot. Requesting an unsupported format for the mode is a hard error raised before any dependency install (it used to silently fall back to a supported format with only a warning, which is unsafe for CI — see upload-sarif). Deliberately has no Action-level default (unlike most inputs): a single default here would resolve to the wrong value for scan regardless of mode, since GitHub Actions has no way to tell "left unset" from "explicitly set to the default" apart once a default is declared — each mode branch in action/run.sh supplies its own correct default instead. no
output-filePath to write the report file. If not set, output goes to stdout and job summary.no
snapshot-compressiondump mode only (ADR-059). Storage envelope for the written snapshot: 'auto' (default) infers gzip/zstd/plain from output-file's canonical suffix (.abicheck.json.gz/.abicheck.json.zst/plain .abicheck.json); an explicit 'none', 'gzip', or 'zstd' is used as-is and is a hard error if it contradicts output-file's suffix, mirroring the CLI's own --compression flag it maps to directly. Ignored by every other mode -- compare/scan/deps-tree/deps-compare produce a report, not a stored snapshot, and transparently read a compressed snapshot operand either way via magic-byte detection regardless of this input. no
policyBuilt-in policy profile: strict_abi (default), sdk_vendor, plugin_abi.nostrict_abi
policy-filePath to a YAML policy document with per-kind (overrides:) or selector-scoped (reclassify:) verdict re-classification, or the name of a packaged built-in one (e.g. `security`). Forwarded as `--policy`, which takes a profile name or a document; setting this outranks `policy` for the run, exactly as the removed `--policy-file` flag did. no
suppressPath to a YAML suppression file to filter known/intentional changes.no
verboseEnable verbose/debug output from abicheck.nofalse
python-versionPython version for setup-python.no3.13
install-depsDeprecated — use `dependency-source` instead (kept for one release cycle). Ignored if `dependency-source` is set. true (the default) maps to dependency-source=conda-forge; false maps to dependency-source=none. notrue
dependency-sourceHow to install system dependencies (castxml, gcc/g++, clang, bear): 'conda-forge' (default — this repo's pixi-managed `scanner` conda-forge environment, castxml 0.7.x + whichever gcc/g++ conda-forge currently resolves as default; Linux/macOS only, no clang, no bear), 'conda-forge-gcc14' (same, but pinned to gcc/g++ 14.x instead of whatever's currently default; Linux-only, no clang, no bear), 'conda-forge-clang20' (castxml + clang/clang++ 20.x instead of gcc — the one conda-forge source that provides clang, needed for L4 source-ABI replay and the clang-backed call/type/include-graph edges of L5 (the structural L5 source graph itself still builds from L3 build evidence alone, without clang); Linux/macOS, no bear), 'system' (apt/Homebrew + the pinned CastXML Superbuild — the previous default, still available; installs bear on Linux only — the macOS path installs only castxml via Homebrew and relies on Xcode's preinstalled clang, no bear), or 'none' (skip; dependencies must already be on PATH). Leave unset to fall back to `install-deps` for backward compatibility. **On a Windows runner the unset default is 'system', not 'conda-forge'** — every conda-forge source is unsupported there, so the usual default would hard-fail before analysis; an explicitly requested conda-forge* still errors rather than being silently rewritten. no""
upload-sarifUpload SARIF results to GitHub Code Scanning (requires security-events: write permission). Requires format=sarif and mode=compare — setting this with any other mode or format is a hard error raised before any dependency install (mode=scan/dump/deps-tree/deps-compare never produce a SARIF report). nofalse
fail-on-breakingFail the step when a binary ABI break is detected (exit code 4).notrue
fail-on-api-breakFail the step when a source-level API break is detected (exit code 2).nofalse
severity-presetSeverity preset: 'default', 'strict', or 'info-only'. Controls exit codes and report labels. Only applies to compare mode. no
require-complete-analysisFail the step (P0.4's orthogonal ANALYSIS_INCOMPLETE axis) when analysis_assurance.status is not 'complete', independent of the compatibility verdict. Mirrors `compare`/`scan --against`'s own --require-complete-analysis flag. Applies to single-pair compare (old-library/new-library are individual files, not a directory or package) and scan (--against) modes only -- rejected outright (step fails) for a directory/package compare's per-library release fan-out, which has no single analysis_assurance result to gate on. Has no effect for an audit-only scan (new-library with no against/abi-baseline): the CLI itself rejects this flag without a baseline, so run.sh never forwards it in that shape. A dedicated input rather than routing through extra-args, so this Action's own detection of whether the flag was requested is never ambiguous with a value some other option in extra-args happens to spell the same way. nofalse
annotateEmit GitHub Actions workflow command annotations (inline PR-diff comments) for ABI/API findings, in compare mode (single-pair and directory/package release operands alike). Rendered by this Action itself from the run's own persisted JSON report (schema 2.43+), not by passing --annotate to the abicheck CLI -- no second comparison is ever run to collect them, for either operand shape. Has no effect on scan mode as of this Action version. A dedicated input rather than routing through extra-args, for the same reason require-complete-analysis is: this Action's own detection of whether annotations were requested is never ambiguous with some other extra-args value. nofalse
annotate-additionsInclude additions and other compatible-but-notable changes as ::notice annotations. Off by default (only errors/warnings, plus the one notice kind that is always shown regardless of this input -- a --contract finding compatibility policy did not evaluate). Has no effect unless annotate is also true. nofalse
extra-argsAdditional CLI arguments passed directly to the abicheck command.no""
add-job-summaryWrite a markdown summary to the GitHub Actions Job Summary. Ignored for dump mode.notrue
pr-commentPost a sticky ABI report comment on the pull request. Supports compare mode (including directory/package operands) and scan mode (a single artifact, not scan --artifact-set — that JSON has no single-artifact shape to render), rendering the same verdict/breaking/needs-review sections plus a green "Public API additions" table for either mode, and a scan-only risk/coverage summary line. The comment is a content channel and never changes the check's red/green state — breaks still gate via fail-on-breaking / fail-on-api-break. Defaults to 'true', but is a no-op unless the workflow is triggered by a pull_request event and a token with 'pull-requests: write' is available. notrue
pr-comment-mode'update' (default) keeps a single sticky comment and edits it in place on every run; 'new' posts a fresh comment each run. Either way the scanned head SHA is shown in the comment. noupdate
pr-comment-onWhen to comment: 'changes' (default) only comments when there is at least one ABI/API change, 'always' comments every run (including a clean "no changes" result), 'never' disables the comment. nochanges
pr-comment-detailDetail level of the comment body: 'summary' (verdict + counts only), 'standard' (default; per-symbol tables for breaking/review, grouped safe list) or 'full' (every change with locations, all sections expanded). nostandard
github-tokenToken used to post the PR comment and to auto-fetch release baselines. Defaults to the workflow token; requires 'pull-requests: write' for the comment. no${{ github.token }}
namedescription
verdictABI verdict. For compare (single-pair or directory/package operands): COMPATIBLE, COMPATIBLE_WITH_RISK (a real, exit-0 tier -- the report found a compatible-but-risky change, e.g. a toolchain-floor raise; R1, CLI-audit: this was previously laundered into plain COMPATIBLE at exit 0, silently dropping every risk finding from this Action's own output), SEVERITY_ERROR (with the severity-preset input, or when a `severity.addition: error` key in the repository's .abicheck.yml detects new public API additions), COVERAGE_INCOMPLETE (with --contract via extra-args, when the selected contract domain cannot be closed on the available evidence -- ADR-049's orthogonal coverage axis, which leaves the compatibility verdict unchanged), SCOPE_INCOMPLETE (directory/package operands only, ADR-065: a selected member went unchecked under `.abicheck.yml`'s `scope.on_incomplete: block` key, or the run completed no comparison at all -- the completeness axis, orthogonal to the compatibility verdict, which then covers the compared members only; fails the step unconditionally, since no fail-on-* input governs it), ANALYSIS_INCOMPLETE (single-pair operands only, with the require-complete-analysis input, when analysis_assurance.status is not "complete" -- P0.4's orthogonal assurance axis, which likewise leaves the compatibility verdict unchanged; rejected outright for a directory/package operand, so this verdict never applies there), API_BREAK, BREAKING, REMOVED_LIBRARY (directory/package operands with fail-on-removed-library set), or ERROR. For dump: COMPATIBLE or ERROR. For scan: COMPATIBLE, COMPATIBLE_WITH_RISK (same exit-0 risk tier as compare's, above), SEVERITY_ERROR (with --against and a severity setting, when an error-level category has findings), COVERAGE_INCOMPLETE, ANALYSIS_INCOMPLETE (with --against and the require-complete-analysis input, same axis as compare's), API_BREAK, BREAKING, NOT_COMPARABLE (with --against, when the candidate and baseline were not extracted under a comparable profile/scope contract -- ADR-050 D2 -- so no comparison could run at all; this verdict fails the step unconditionally, since no fail-on-* input governs a run in which no comparison happened), BUDGET_OVERFLOW, EVIDENCE_CONTRACT_ERROR (this scan's evidence contract could not be satisfied -- ADR-037 D5 -- so no comparison could run at all; two independent causes share this one verdict: a pinned --depth/--source-method whose required source evidence was never collected, or --abi3 targeting a binary that isn't a recognisable CPython extension module -- see the command's own error message for which one applied. Like NOT_COMPARABLE, this fails the step unconditionally and is distinct from ERROR, which covers a genuine CLI/config error. For a single ARTIFACT (not --artifact-set), published unconditionally as of 2026-09-03 -- the scan process's own dedicated exit code (`cli_scan.py`'s `_EXIT_EVIDENCE_CONTRACT_ERROR`, 7) makes this axis unambiguous to `action/run.sh`'s exit-code dispatch regardless of format, pr-comment setting, or extra-args, closing a gap four successive review rounds found in three earlier signaling designs (a stderr marker line, then a marker-file path, each shown forgeable by a PR-controlled build script the scan's own evidence collection spawns -- see ADR-064 for the full account). Whether a JSON report also exists for this Action to read (auto-injected `--write json=...` sidecar, extra-args naming one, `format: json`, or extra-args overriding `--format` in either direction, detected via `_effective_format`) still decides only whether the sticky PR comment and JSON-consuming outputs have structured detail to show, not whether this verdict/exit-code publish at all. new-library-set (--artifact-set) scans reach this same dedicated exit code too, as of 2026-09-04: `service_scan._aggregate_scan_set_verdict` reports the set's own top-level verdict/exit as EVIDENCE_CONTRACT_ERROR/7 whenever a member's `_EvidenceContractError` is the *worst* outcome across the set -- unconditionally, independent of `--format`/pr-comment/extra-args, the process's own exit code needing no JSON report to be legible. A sibling library with a real API_BREAK/BREAKING still outranks it (that verdict/exit stays at the set's top level instead, per that function's own docstring), leaving the aborted member's status visible only in the JSON report's own per_artifact list, not in the verdict/exit-code outputs this Action publishes -- so "unconditionally fails the step" holds for a new-library-set scan only when the evidence-contract abort is the set's own worst outcome; when a sibling member's real break outranks it, this Action's fail-on-api-break/fail-on-breaking inputs alone decide the step's outcome, exactly as for an ordinary API_BREAK/BREAKING verdict, with the sibling abort unreported by this Action), or ERROR. For deps-compare: PASS, WARN, FAIL, or ERROR. For deps-tree: PASS, FAIL, or ERROR. On compare and scan alike, verdict and exit-code are different axes and may disagree: BREAKING/API_BREAK follows the report's compatibility verdict (what was *detected*) while the exit code follows what the severity policy chose to *gate* on. So a demoting policy (e.g. --severity-preset info-only) can publish BREAKING with exit code 0 (nothing gated), and a policy that demotes abi_breaking while some other gate still fires -- an error-level --crosscheck, or potential_breaking: error -- can publish BREAKING with exit code 2. The same holds at exit code 1: a demoted abi_breaking alongside an error-level addition/quality finding publishes BREAKING or API_BREAK while the exit code names the severity tier that gated, so exit 1 is not always SEVERITY_ERROR or COVERAGE_INCOMPLETE. In both cases the verdict stays truthful and the step still gates at the tier the exit code names, so fail-on-breaking does not re-gate a break the policy demoted. Branch on verdict for what was found, and on exit-code for the tier abicheck itself gated at. Note that neither is the same as whether this step failed: the fail-on-* inputs decide that, so an ordinary API break with the default fail-on-api-break: false publishes exit-code 2 from a step that succeeded. Use the step's own outcome/conclusion for that.
exit-codeabicheck exit code. compare: 0 (compatible), 1 (severity error, incomplete contract coverage, incomplete analysis assurance with the require-complete-analysis input, or -- directory/package operands, ADR-065 -- an incompletely checked comparison scope under `.abicheck.yml`'s scope.on_incomplete: block key, or no comparison completed at all under either setting; the four share the code and are told apart by the report's pre-fold severity.exit_code / contract_coverage_exit_contribution / analysis_assurance.status / the exit block's incomplete_scope_contribution and no_comparison_completed_contribution), 2 (API break), 4 (ABI break), 8 (library proven removed; directory/package operands with fail-on-removed-library set and a proven-complete NEW inventory). scan: 0 (compatible/advisory), 1 (with --against: severity error, incomplete contract coverage, or incomplete analysis assurance with the require-complete-analysis input -- as on compare, the three share the code and are told apart the same way, one level down under diff), 2 (API break), 4 (ABI break), 5 (budget overflow), 6 (not comparable -- candidate/baseline scope or profile mismatch, VERDICT=NOT_COMPARABLE), 7 (evidence-contract error, ADR-037 D5, VERDICT=EVIDENCE_CONTRACT_ERROR -- either a pinned --depth/--source-method whose required source evidence was never collected, or --abi3 targeting a binary that isn't a recognisable CPython extension module; reachable on an audit-only scan too, since the underlying precondition checks run regardless of whether a baseline comparison follows; this process's own dedicated exit code as of 2026-09-03, published unconditionally for a single ARTIFACT, and for --artifact-set too as of 2026-09-04 when a member's abort is the set's own worst outcome -- see the verdict output's own description above for the exact condition). deps-compare: 0 (pass), 1 (warn), 4 (fail). deps-tree: 0 (ok), 1 (missing). Click CLI errors are mapped to VERDICT=ERROR.
report-pathPath to the generated report file. Set when output-file is provided, or auto-populated for SARIF format (defaults to abicheck-results.sarif). Withheld (empty) when format: sarif and upload-sarif: true were both requested but extra-args overrode --format away from sarif -- the real file exists but its content isn't SARIF, and this is the exact value the upload-sarif step below gates on, so withholding it skips that upload rather than feeding mismatched content to CodeQL.