Skip to content

GitHub Action Inputs and Outputs Reference

Every with: input and outputs.* value for the abicheck GitHub Action, generated directly from action.yml — see that page for setup, mode/input compatibility, and usage recipes; this page is the exhaustive field list only.

Inputs (84)

Input Required Default Description
mode no compare Operation mode. The Action exposes four analysis modes, mirroring abicheck's core per-library CLI commands (compare/dump/deps tree/deps compare; the CLI additionally has project-orchestration commands like aggregate and project plan 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); omitting old-library (and abi-baseline) runs an audit-only compare --no-baseline against new-library alone, reporting no old/new compatibility verdict — see the migration section of docs/use/github-action.md for converting a mode: scan job (retired) to this shape, including the severity-preset requirement to keep it gating a CI job the way mode: scan's own audit mode did; "dump" generates a JSON baseline snapshot from a single library (now also embeds optional L3/L4/L5 build-source evidence via source/build inputs); "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). mode: scan itself is retired outright (hard removal, no deprecation window) — setting it fails the step with an error naming the replacement for your shape.
old-library no — Path to the old library or JSON snapshot. Required for a two-sided compare; omit it (and abi-baseline) to run an audit-only compare --no-baseline against new-library alone instead (the replacement for a baseline-less mode: scan/audit: true request — see the mode input's own description). May also be a directory or package (RPM, Deb, tar, conda, wheel) — abicheck then fans out to a per-library comparison automatically. May also be a stored BundleFacts document written by an earlier run's --bundle-facts-out (plain .json, a compressed .json.gz/.json.zst, or the content-addressed archive form): no separate input or flag selects that shape, since compare classifies the operand from the file's own content, so pointing this input at such a document is all a stored-OLD-side bundle comparison needs. A real large-toolkit document may exceed the conservative pre-json.loads() decode-node ceiling; raise it with resource_limits.max_bundle_facts_decode_nodes in a .abicheck.yml passed as build-config (an explicit, operator-reviewed config — an auto-discovered one may only lower that budget, never raise it, since it can come from the same untrusted checkout being decoded). Producing such a document is --bundle-facts-out, which stays an extra-args option rather than a first-class input: it names one pipeline stage's output artifact path, and a dedicated input would add a second spelling for something extra-args already forwards and validates.
new-library no — Path to the new (current) library, binary, or JSON snapshot. Required for compare (both the two-sided and audit-only shapes), dump (unless a source-only sources/build-info/compile-db input is given), and deps-tree/deps-compare modes. May be a directory or package (RPM, Deb, tar, conda, wheel) ONLY in a two-sided compare, matching old-library — compare then fans out to a per-library comparison automatically. dump, and compare's own audit-only shape, each analyse exactly one artifact and reject a directory/package with an error (neither has a per-library fan-out); for a multi-library release, dump each library individually or use a two-sided compare instead.
new-library-set no — RETIRED (hard removal, no deprecation window). It mapped to the now fully-removed scan --artifact-set/mode: scan. Still declared so a workflow that sets it gets an explicit ::error:: instead of a silently-ignored input; remove it and compare each library individually. For a multi-library release comparison against an old side, use mode: compare with a directory/package operand instead.
debug-info1 no — Debug info package for old side (RPM/Deb/tar). compare mode, directory/package operands only.
debug-info2 no — Debug info package for new side (RPM/Deb/tar). compare mode, directory/package operands only.
devel-pkg1 no — Development package with headers for old side. compare mode, directory/package operands only.
devel-pkg2 no — Development package with headers for new side. compare mode, directory/package operands only.
dso-only no — Only 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.
include-private-dso no — Include 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.
fail-on-removed-library no — Exit 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; 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.
header no — Public 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.
old-header no — Public header(s) for the old side only (overrides header for old). Space-separated.
new-header no — Public header(s) for the new side only (overrides header for new). Space-separated.
public-header-dir no — Directory 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). compare's audit-only shape (old-library/abi-baseline both omitted) forwards it the same way, as a bare -H root applying to the one audited artifact — a two-sided compare has no equivalent flag.
build-target no — RETIRED (hard removal, no deprecation window). scan --build-target was retired first; dump --build-target, which this input mapped to, was retired next, once that removal resolved the routing hazard that had deferred it — neither command has this flag any more. Still declared so a workflow that sets it gets an explicit ::error:: naming the replacement instead of a silently-ignored input: put the root target(s) in .abicheck.yml's build.targets (Bazel only so far, e.g. targets: ["//:math"]) and pass the config with mode: dump's build-config: input, or let it auto-discover from sources:. compare has no equivalent.
include no — Extra 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 #includes 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 a two-sided compare, old-include/new-include replace this list for their side rather than adding to it (repeat any shared directory there too); in dump and compare's audit-only shape, 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.)
old-include no — Include directories for old side only. Space-separated. Used by a two-sided compare; not applicable to compare's audit-only shape, which has no old side. Replaces include for this side rather than adding to it (repeat any directory include also lists that's still needed).
new-include no — Include directories for new side only. Space-separated. In a two-sided compare, replaces include for this side rather than adding to it (repeat any directory include also lists that the new side still needs); in dump and compare's audit-only shape, it is added alongside include instead.
old-version no old Version label for the old library (embedded in reports).
new-version no new Version label for the new library (embedded in reports).
lang no c++ Language mode for header AST parsing: "c++" or "c".
ast-frontend no — L2 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 and compare mode, on single-pair and directory/package (release) operands alike — the per-library release fan-out threads this both-sides L2 context to every pair's header dump (cli_resolve.resolve_directory_compile_context), so it is forwarded, not rejected, for a directory/package operand. (Only a sided old=/new= frontend override has no per-library-pair meaning across a release; this input has no sided spelling.) Same as the ABICHECK_AST_FRONTEND env var.
gcc-path no — Path to GCC/G++ (or clang) cross-compiler binary. Like dump and single-pair compare (including the audit-only shape), it folds into a synthesized .abicheck.yml compile: block forwarded via --config (together with gcc-prefix, gcc-options, sysroot, and nostdinc, when any of those are also set). Applies to dump and compare mode, on single-pair and directory/package (release) operands alike (same scope 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.
gcc-prefix no — Cross-toolchain prefix, e.g. aarch64-linux-gnu-. Folds into the same synthesized compile: block gcc-path describes above, on every mode that accepts it (a full gcc-path wins if both are set, since the merged compile.compiler config key can only hold one). Applies to dump and compare mode, on single-pair and directory/package (release) operands alike (same scope as ast-frontend above).
gcc-options no — Extra compiler flags passed through to the header frontend. Folds into a synthesized compile.options list in the same synthesized compile: block gcc-path describes above, on every mode that accepts it. 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, but that one flag still contains a space internally); a multi-line (YAML block scalar) value treats each line as one already-complete flag, not split further. Either way, each resulting compile.options entry must be a single whitespace-free flag -- a flag containing a space (whether from a quoted single-line segment or a spaced multi-line entry) is rejected with a clear error rather than silently accepted or silently split further; put each word needing its own flag on its own line instead. This is a real, accepted narrowing from an earlier Action version, where the CLI's own now-removed --compiler-option flag once forwarded such a flag verbatim (a raw CLI arg is not subject to compile.options' own whitespace-free-atom contract). Applies to dump and compare mode, on single-pair and directory/package (release) operands alike (same scope as ast-frontend above).
sysroot no — Alternative system root directory for cross-compilation. On dump and single-pair compare (including the audit-only shape) this folds into the same synthesized compile: block gcc-path describes above (deps-tree keeps its own direct --sysroot CLI forwarding, unaffected -- it is not part of this compile-context group at all). Applies to dump, deps-tree, and compare mode, the latter on single-pair and directory/package (release) operands alike (same scope as ast-frontend above).
nostdinc no false Do 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 and compare mode, on single-pair and directory/package (release) operands alike (same scope as ast-frontend above).
follow-deps no false Include transitive dependency graph and symbol binding info in dump or compare output. ELF only. Applies to dump and compare modes. For compare, requires a baseline (old-library or abi-baseline) -- rejected outright for the audit-only shape (compare --no-baseline's own DT_NEEDED dependency walk is not wired to that path yet).
old-root no — Sysroot for the old (baseline) environment. Required for mode=deps-compare.
new-root no — Sysroot for the new (candidate) environment. Required for mode=deps-compare.
search-path no — Additional directories to search for shared libraries (space-separated). Used by deps-tree, deps-compare, and follow-deps.
ld-library-path no — Simulated LD_LIBRARY_PATH (colon-separated). Used by deps-tree, deps-compare, and follow-deps.
used-by no — Application 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). Requires a baseline (old-library or abi-baseline) -- rejected outright for the audit-only shape (consumer scoping needs two versions to compare, and an audit has none). compare mode only.
used-by-manifest no — Path(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 a json export). Mutually exclusive with required-symbol/required-symbols. compare mode only. Requires a baseline (old-library or abi-baseline) -- rejected outright for the audit-only shape, same reason as used-by above.
required-symbol no — An 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. Requires a baseline (old-library or abi-baseline) -- rejected outright for the audit-only shape, same reason as used-by above.
required-symbols no — Path to a file of required symbols, one per line (blank lines and '#' comments ignored) -- combined with any required-symbol values. compare mode only. Requires a baseline (old-library or abi-baseline) -- rejected outright for the audit-only shape, same reason as used-by above.
against no — RETIRED (hard removal). This applied only to the now-removed mode: scan, as the baseline artifact to compare the scanned new-library against. Set old-library (or abi-baseline) to the same value under mode: compare instead. Still declared so a workflow that sets it gets an explicit ::error:: naming that replacement instead of a silently-ignored input.
sources no — Source 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 dump mode, 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; not applicable to compare's audit-only shape, which has no old side).
build-info no — Out-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 dump mode, and by compare mode for the new (candidate) side only — mapped to compare's --build-info new=... (same old-side caveat as sources above).
compile-db no — Explicit path to a compile_commands.json, when it is not under sources. Folded into --build-info for every mode that accepts it (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.
build-config no — Path to a trusted .abicheck.yml. For dump mode, supplying this alone is enough — an explicit --config is itself the operator's consent to run its build.query. Used by dump mode, 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); cohorts: (the SONAME-skew check) applies to compare only.
jobs no — Removed and ignored. 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.
bundle-system-providers no — RETIRED (CLI cleanup phase two, PR J — hard removal). The cross-library bundle-analysis layer's system-provider allow-list extension was removed here and 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. (Marked RETIRED, like every other hard-error tombstone here, so tests/test_action_retired_inputs_contract.py holds it to the same executable fail-closed guarantee; jobs above is the softer warn-only class and deliberately keeps its own wording.)
allow-build-query no false Deprecated 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.
depth no — Evidence-depth selector: binary, headers, build, or source. Used by dump mode, and by compare mode (--depth) -- both the two-sided and audit-only shapes. Omitting it means 'headers' -- set it to build or source explicitly to collect those (there is no risk-driven auto escalation). --depth source analyses the whole current library target unless since/changed-path seeds a narrower changed scope. Maps to --depth. For a compare directory/package (release) operand, every rung is forwarded per member exactly as for a single pair. build and source are reachable there without any inline evidence input when the members are pre-dumped snapshots that already carry it (dump --sources/--build-info); what a release operand does not support is the inline sources/build-info/compile-db inputs themselves, which are rejected with an error before the run since the per-library fan-out never collects them. Note what pinning a rung does and does not guarantee: a shortfall is reported (the exit-7 evidence-contract axis) only for a pinned build/source rung on a side this run extracts live. A member that is already a serialized snapshot was never extracted by this run, so no shortfall is reported for it -- a successful run is therefore not by itself proof that every member carried the requested evidence.
since no — compare mode (single-pair operands, two-sided only). Focus the run's source-evidence scope on files changed vs a ref (e.g. origin/main). Maps to compare --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). Rejected outright (step fails before any dependency install) for compare's audit-only shape (old-library and abi-baseline both omitted): compare --no-baseline does not implement revision-range evidence scoping -- set old-library (or abi-baseline) to use since.
changed-path no — compare mode (single-pair operands, two-sided only). Changed path(s) to focus the run's source-evidence scope on (space-separated; alternative to since). Maps to repeated compare --changed-path. Not forwarded for a compare directory/package operand, same as since above. Rejected outright for compare's audit-only shape, same as since above and for the same reason.
budget no — compare mode, two-sided shape only. Time guard (e.g. 15m, 900s, 1h). The step FAILS on overflow (exit 5) — a budget never silently shrinks scope. Maps to compare --budget. Rejected outright (step fails before any dependency install) for compare's audit-only shape (old-library and abi-baseline both omitted): compare --no-baseline does not wire the wall-clock guard to that path yet -- set old-library (or abi-baseline) to use budget.
dry-run no false Resolve 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 -- both the two-sided and audit-only shapes -- dump, deps-tree, deps-compare). To preview an audit-only single-build hygiene lint instead of a two-sided compare, simply omit old-library and abi-baseline for that step.
estimate no false RETIRED. This applied only to the now-removed mode: scan, as a dry-run alias. Use dry-run: 'true' instead, which applies to every mode. Still declared so a workflow that sets it gets an explicit ::error:: naming that replacement instead of a silently-ignored input.
audit no false RETIRED. This applied only to the now-removed mode: scan, forcing a one-build audit-only run. Under mode: compare, simply omit old-library and abi-baseline to run an audit-only compare --no-baseline instead; set severity-preset (e.g. 'default') if this job should still gate on a BREAKING/API_BREAK-classified finding the way mode: scan's own audit mode always did. Still declared so a workflow that sets it gets an explicit ::error:: naming that replacement instead of a silently-ignored input.
crosscheck no — RETIRED (superseded, not a hard removal with nothing to replace it). scan --crosscheck's KEY=LEVEL promotion syntax, which this input mapped to, no longer exists (along with mode: scan itself): every cross-source check it used to gate already reaches compare as an ordinary ChangeKind, so policy / .abicheck.yml's policy.overrides.<CHANGE_KIND>: error already lets you control any one check's severity — only the KEY=LEVEL syntax itself doesn't survive. Still declared so a workflow that sets it gets an explicit ::error:: naming that replacement instead of a silently-ignored input.
risk-rules no — RETIRED (hard removal). scan --risk-rules is gone, together with mode: scan itself and the risk-driven auto depth escalation it fed: an omitted depth now resolves to the fixed headers rung — the same default compare has always used — instead of being scored from the changed paths. Set depth: source (or build) explicitly to pin the level a risk profile used to escalate to. Still declared so a workflow that sets it gets an explicit ::error:: saying so.
abi-baseline no — Automatically fetch an ABI baseline snapshot for compare mode, used as old-library. 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 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.
baseline-profile no — Selects 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.
baseline-target no — The 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.
baseline-asset-name-template no abicheck-baseline-{profile}.tar.zst Release 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.
baseline-generation no — Substituted 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.
format no — Output format: json, markdown, sarif, html, junit, review, or oneline (each mode's default when this input is left unset). compare, two-sided shape, single-pair (non-directory, non-package) operand: all seven. compare, two-sided shape, directory/package operand: json, markdown, junit, oneline, or html (a release page rendered from the release JSON) — sarif/review are rejected with an error (the per-library release fan-out has no single report those formats describe). compare against a stored BundleFacts baseline (old-library naming such a document, new-library a directory/package): json or markdown only — that driver renders no junit either. This one narrowing is NOT pre-checked before the dependency install, unlike every other row here: deciding it needs the document's own content (see the old-library input), and neither re-implementing that classifier in shell nor reading an untrusted document at preflight is acceptable, so -o junit=... on this shape fails in the CLI instead. Tracked in docs/contribute/known-gaps.md. compare, audit-only shape (old-library/abi-baseline both omitted): json, markdown, sarif, junit, or oneline — html and review are two-sided-report renderers with no audit-only equivalent and are rejected with an error. html is also supported by deps-tree/deps-compare (a dependency-stack report); sarif/junit/review/oneline are not. dump ignores this input; it always writes a JSON snapshot. Requesting an unsupported format for the mode/shape 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): 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.
output-file no — Path to write the report file. If not set, output goes to stdout and job summary.
snapshot-compression no — dump mode only. 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/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.
policy no strict_abi Built-in policy profile: strict_abi (default), sdk_vendor, plugin_abi.
policy-file no — Path 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.
suppress no — Path to a YAML suppression file to filter known/intentional changes.
verbose no false Enable verbose/debug output from abicheck.
python-version no 3.13 Python version for setup-python.
install-deps no true Deprecated — 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.
dependency-source no — How 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.
upload-sarif no false Upload 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=dump/deps-tree/deps-compare never produce a SARIF report).
fail-on-breaking no true Fail the step when a binary ABI break is detected (exit code 4).
fail-on-api-break no false Fail the step when a source-level API break is detected (exit code 2).
severity-preset no — Severity preset: 'default', 'strict', or 'info-only'. Controls exit codes and report labels. Applies to compare mode -- both the two-sided shape and the audit-only shape, where it is also the required opt-in for the AUDIT_GATE audit-gate axis: that axis is inactive by default (see the AUDIT_GATE verdict entry below), so set this to a value other than 'info-only' to gate an audit-only run on a BREAKING/API_BREAK-classified finding. Does not apply to dump, deps-tree, or deps-compare.
require-complete-analysis no false RETIRED (rulings.py deferred-option followup -- hard removal, no deprecation window): removed and no longer forwarded. The CLI's own compare --require-complete-analysis flag it mirrored is gone entirely: P0.4's orthogonal ANALYSIS_INCOMPLETE axis is config-only now, .abicheck.yml's assurance.require_complete: true, with no CLI override. Applied identically to single-pair compare's two-sided and audit-only (--no-baseline) shapes before its removal, and rejected outright for a directory/package compare's per-library release fan-out until that operand was given real semantics: the fan-out folds every compared member's own analysis_assurance with max into the same exit axis, so assurance.require_complete: true is no longer single-pair-only. Still declared here so a workflow that sets it gets an explicit ::error:: naming that replacement instead of a silently-ignored input; set assurance.require_complete: true in your .abicheck.yml and pass that file as build-config instead, then remove this input.
annotate no false Emit GitHub Actions workflow command annotations (inline PR-diff comments) for ABI/API findings, in compare mode's two-sided shape (single-pair and directory/package release operands alike). Not supported for compare's audit-only shape (old-library/abi-baseline both omitted) -- its report has no additions/removals to annotate, only candidate-side findings; requesting it there emits a ::notice:: explaining why and renders nothing, rather than silently producing zero annotations. 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 dump/deps-tree/deps-compare. A dedicated input rather than routing through extra-args: this Action's own detection of whether annotations were requested must never be ambiguous with some other extra-args value, which a free-text passthrough can't guarantee.
annotate-additions no false Include 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.
extra-args no — Additional CLI arguments passed directly to the abicheck command.
add-job-summary no true Write a markdown summary to the GitHub Actions Job Summary. Ignored for dump mode.
pr-comment no true Post a sticky ABI report comment on the pull request. Supports compare mode (both the two-sided shape, including directory/package operands, and the single-artifact audit-only shape), rendering the same verdict/breaking/needs-review sections plus a green "Public API additions" table, and (audit-only) a 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.
pr-comment-mode no update '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.
pr-comment-on no changes When to comment: 'changes' (default) comments when the run produced anything a reviewer must act on -- at least one ABI/API change, a removed/added library, an incompletely checked comparison scope, a finding disposed of by a suppression rule, or a material analysis limitation the comparison itself recorded (its 'coverage_warnings', e.g. "No header/AST data; type-level changes may be missed"). A clean run whose only evidence note is a permanently-inapplicable detector (the PE and Mach-O detectors on an ELF comparison) is not such an outcome and still posts nothing. 'always' comments every run (including a clean "no changes" result), 'never' disables the comment unconditionally. Changing this input never changes the compatibility verdict, the gate, or the step's exit code -- it only decides whether a comment is written.
pr-comment-report-artifact-url no — Optional direct URL of an uploaded full-report artifact (JSON/HTML), linked in the comment footer as 'Download full report' -- distinct from the always-present 'View workflow run' link. Leave unset unless an upload step actually succeeded: set it from that step's own 'artifact-url' output, so a link in the comment always corresponds to an artifact that exists. This Action does not upload artifacts itself, so it cannot derive this value.
pr-comment-detail no standard Detail 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).
github-token no ${{ github.token }} Token used to post the PR comment and to auto-fetch release baselines. Defaults to the workflow token; requires 'pull-requests: write' for the comment.

Outputs (3)

Output Description
verdict ABI verdict. For compare (single-pair or directory/package operands, two-sided shape): 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 -- the orthogonal coverage axis, which leaves the compatibility verdict unchanged), SCOPE_INCOMPLETE (directory/package operands only: 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), ADDITIONS_UNACKNOWLEDGED (single-pair compare with .abicheck.yml's acknowledgment.unacknowledged_additions: block, when public additions are not covered by a record in the acknowledgment.file it names -- the additions-review axis, orthogonal to the compatibility verdict: the additions keep their verdict; fails the step unconditionally, since no fail-on- input governs it), ANALYSIS_INCOMPLETE (with .abicheck.yml's assurance.require_complete: true, when analysis_assurance.status is not "complete" -- P0.4's orthogonal assurance axis, which likewise leaves the compatibility verdict unchanged. Applies at any operand cardinality: for a directory/package operand it is max over every compared member's own contribution, so one member short of complete analysis publishes this verdict for the whole release, and the release JSON's analysis_assurance block names which members fell short. It used to be rejected outright for that operand, and this output declared itself single-pair-only on the strength of that rejection), API_BREAK, BREAKING, REMOVED_LIBRARY (directory/package operands with fail-on-removed-library set), ERROR, or REPORT_UNREADABLE (abicheck itself exited 0, but the JSON report you requested was absent, truncated, unparseable, not a JSON object, an empty one, or a document that parsed cleanly while carrying no abicheck result at all (no verdict, no run_outcome, no findings) -- so nothing read this run's result. At exit 0 it also covers a report whose analysis_assurance block has no analysis_assurance_exit_contribution beside it on a schema that emits the two together, which makes it internally inconsistent rather than passing. That last case is scoped to exit 0 deliberately: at a nonzero exit the report's own compatibility verdict IS readable, and the two axes are orthogonal, so the real verdict (BREAKING/API_BREAK/...) is published rather than erased -- replacing an established break with "no result" would discard evidence, which is the opposite of this value's purpose. The inconsistency still fails the step unconditionally and still emits its own error annotation on every exit path; only the verdict label differs. Fails the step unconditionally and is never waived by a fail-on- input: none of those is a statement that an unread result should be reported as a pass. Distinct from ERROR, which is abicheck reporting its own failure through a non-zero exit code; here the tool claimed success and the report backing that claim is missing or unusable. "Requested" means format: json (whether it lands in output-file or on stdout) or your own extra-args -o json=PATH; with a non-json format, the absence of this Action's own internal sidecar still yields COMPATIBLE, since exit 0 does establish that abicheck's gate passed even when the tier cannot be read. Previously every one of these states published COMPATIBLE). For compare's audit-only shape (old-library and abi-baseline both omitted -- the replacement for a baseline-less mode: scan/audit: true request): COVERAGE_INCOMPLETE (with --contract via extra-args, when the selected contract domain cannot be closed on the available evidence -- the same orthogonal coverage axis as the two-sided shape above; unlike the two-sided shape, there is no bare severity-gate exit 1 here -- severity-based audit gating uses the dedicated exit 3 below instead. This is one of exit 1's three causes on this shape, alongside ANALYSIS_INCOMPLETE below and a dry-run-only preview of the exit-7 blocker; see the exit-code output's own description for all three), AUDIT_GATE (a real, BREAKING/API_BREAK-classified finding was detected against the candidate's own public surface while a severity preset other than info-only was in effect. Gating is opt-in: this Action never injects a preset on your behalf, so you must set the severity-preset input yourself (e.g. default) to get this axis at all -- unlike legacy mode: scan's own audit mode, which gated by default with no flag needed; see the migration notes in this file's mode input and in docs/use/github-action.md. This is NOT a two-sided compatibility verdict -- an audit reports no additions, removals, or compatibility verdict at all; only the candidate-side findings themselves. Exit code 3, fails the step unconditionally, since no fail-on- input governs an axis with no compatibility verdict to follow), AUDIT_CLEAN (exit 0: the same shape as AUDIT_GATE, but no candidate-side finding at all was detected against the candidate's own public surface -- not a two-sided compatibility verdict either, since no baseline was compared), AUDIT_RISK (exit 0: a real candidate-side finding WAS detected, but this run did not gate on it -- no severity-preset opt-in reached a BREAKING/API_BREAK-classified finding, or the only finding present is RISK-classified, which policy/audit_gate_exit.py never gates regardless of preset. Distinct from AUDIT_CLEAN so a reviewer can tell "nothing found" apart from "something found, not gating" -- both exit 0, neither fails the step, but only one has something worth reading in the JSON report's findings[]), DRY_RUN (exit 0, dry-run input true or an effective --dry-run via extra-args: compare --dry-run performs no analysis and writes no report at all, so there is no candidate-side finding -- or absence of one -- to claim either way. Distinct from AUDIT_CLEAN, which asserts a real audit ran and found nothing; see the command preview in the job log for what a real run would do. A two-sided dry run now publishes this same value, for the same reason: a preview performs no comparison, so it has no compatibility result either. That shape used to publish COMPATIBLE, which claimed a result no analysis produced), ANALYSIS_INCOMPLETE (with .abicheck.yml's assurance.require_complete: true, when analysis_assurance.status is not "complete" -- the same P0.4 orthogonal assurance axis as the two-sided shape above, reachable here since that config key applies to both compare shapes; not a two-sided compatibility verdict either, since no baseline was compared -- fails the step unconditionally), REPORT_UNREADABLE (reachable on this shape too, for the same reason and with the same meaning as in the two-sided enumeration above: an exit-0 run whose requested JSON report was missing, unusable, or self-contradictory establishes no result, and an audit's AUDIT_CLEAN/ AUDIT_RISK are results like any other. Listed here explicitly because the enumerations are read per shape and this value was documented only above, leaving the audit-only contract incomplete), NOT_COMPARABLE (two-sided shape only, with the old-library or abi-baseline input: when the candidate and baseline were not extracted under a comparable profile/scope contract, 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. Never reachable on the audit-only shape, which has no baseline to mismatch against), BUDGET_OVERFLOW (two-sided shape only, with the budget input), EVIDENCE_CONTRACT_ERROR (this run's evidence contract could not be satisfied -- so no comparison could run at all: a pinned --depth/--source-method whose required source evidence was never collected. Two-sided shape only for the --abi3-targeting-an-unrecognisable-binary cause -- no_baseline_rulings.py explicitly rejects --abi3 on the audit-only shape as an unsupported option (a usage error, exit 64) rather than reaching this verdict, since stable-ABI auditing is candidate-side and not yet wired to the no-baseline path -- see the command's own error message for which cause applied on the two-sided shape. Like NOT_COMPARABLE, this fails the step unconditionally and is distinct from ERROR, which covers a genuine CLI/config error. The --depth/--source-method cause is reachable on the audit-only shape too, since the underlying precondition checks run regardless of whether a baseline comparison follows -- this axis is 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 run's own evidence collection spawns). Whether a JSON report also exists for this Action to read (auto-injected -o json=... sidecar, extra-args naming one, or format: json; an extra-args export set replaces this Action's own, 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), or ERROR. For dump: COMPATIBLE or ERROR. For deps-compare: PASS, WARN, FAIL, or ERROR. For deps-tree: PASS, FAIL, or ERROR. For a two-sided compare, 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 -- potential_breaking: error, say -- 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-code abicheck exit code. compare, two-sided shape: 0 (compatible), 1 (severity error, incomplete contract coverage, incomplete analysis assurance with .abicheck.yml's assurance.require_complete: true, or -- directory/package operands -- 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). compare, audit-only shape (old-library/abi-baseline both omitted): 0 (compatible/advisory, or DRY_RUN under dry-run: true -- see the verdict output's own description), 1 (incomplete contract coverage, or incomplete analysis assurance with .abicheck.yml's assurance.require_complete: true -- unlike the two-sided shape, there is no bare severity-gate exit-1 source here (severity-based audit gating uses the dedicated exit 3 below instead). A third cause, dry-run only: with dry-run true, a live-candidate (not a stored snapshot) request pinning depth: build/source via extra-args with no sources/build-info given previews a blocker the real run would hit at exit 7 -- the dry-run preview itself reports VERDICT ERROR at exit 1 rather than 7, since no analysis actually ran to collect that evidence contract in the first place. These three are exit 1's only causes on this shape; the first two are told apart the same way, one level down under diff), 3 (AUDIT_GATE -- the audit-gate axis; see the verdict output's own description above), 7 (evidence-contract error, VERDICT=EVIDENCE_CONTRACT_ERROR -- a pinned --depth/--source-method whose required source evidence was never collected; reachable on the audit-only shape too, since the underlying precondition checks run regardless of whether a baseline comparison follows. The other two-sided-shape cause, --abi3 targeting a binary that isn't a recognisable CPython extension module, does NOT reach exit 7 on the audit-only shape -- --abi3 is rejected there upstream as an unsupported option (exit 64 usage error), since stable-ABI auditing isn't wired to the no-baseline path yet -- see the verdict output's own description above). budget/exit-5 does not apply to the audit-only shape (rejected outright upstream, per the budget input's own description). deps-compare: 0 (pass), 1 (warn), 4 (fail). deps-tree: 0 (ok), 1 (missing). Click CLI errors are mapped to VERDICT=ERROR.
report-path Path 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' own exports replaced it with a non-sarif format -- 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.