Skip to content

GitHub Action

abicheck ships as a reusable GitHub Action that you can add to any CI pipeline with a few lines of YAML. It installs Python, system dependencies, and abicheck automatically, then runs ABI comparison and reports results.

Picking a mode or failure policy? See Choose Your Workflow for the decision matrix — which artifacts map to which mode, and which severity inputs gate the build.

Quick start

- uses: abicheck/abicheck@v0.3.0
  with:
    old-library: abi-baseline.json
    new-library: build/libfoo.so
    new-header: include/foo.h

Inputs

Library inputs

Input Required Description
mode no compare (default), compare-release, dump, scan, merge, appcompat, deps, or stack-check
old-library yes (compare, compare-release) Path to old library, JSON snapshot, ABICC dump, directory, or package
new-library yes (compare, dump, scan, …) Path to new library, binary, directory, or package. In scan mode this is the scanned binary or .abi.json snapshot.

Header inputs

Input Required Description
header no Public header file(s) or directory(ies) for both sides (space-separated)
old-header no Header file(s) or directory(ies) for old side only
new-header no Header file(s) or directory(ies) for new side only
include no Extra include dirs for castxml (both sides)
old-include no Include dirs for old side only
new-include no Include dirs for new side only

Evidence layers in the Action

The Action drives the same five-layer evidence model as the CLI. The inputs above cover L0 (old-library/new-library), L1 (debug info — embedded, or debug-info1/debug-info2 packages in compare-release mode), and L2 (header/include).

The deeper layers — L3 build context, L4 source-ABI replay, and L5 source graphs — are now first-class Action inputs. Use the sources/build-info/compile-db inputs in scan or dump mode and abicheck collects them inline; no separate CLI steps are required. See Source scans below and the Build Info & Sources concept guide.

Application compatibility inputs (appcompat mode)

Input Required Description
app-binary yes (appcompat) Path to application binary (ELF, PE, or Mach-O)
check-against no Library for weak-mode symbol availability check (no old library needed)
show-irrelevant no Include library changes not affecting the application (default false)
list-required-symbols no List symbols the app requires and exit (default false)

Version labels

Input Default Description
old-version old Version label for old library
new-version new Version label for new library

Language and compiler

Input Default Description
lang c++ Language mode for the header backend: c++ or c
ast-frontend castxml (auto) L2 header-AST frontend (compare/dump modes): auto, castxml, or clang (clang -ast-dump=json, for clang-only hosts). auto falls back to clang on a castxml toolchain error. Same as ABICHECK_AST_FRONTEND.
gcc-path Path to cross-compiler binary (dump mode only)
gcc-prefix Cross-toolchain prefix, e.g. aarch64-linux-gnu- (dump mode only)
gcc-options Extra flags for castxml (dump mode only)
sysroot Alternative system root (dump and deps modes)
nostdinc false Skip standard include paths (dump mode only)

Full-stack dependency validation (Linux ELF)

Input Default Description
follow-deps false Include transitive dependency graph and symbol bindings in dump/compare output
baseline Sysroot for baseline environment (required for stack-check mode)
candidate Sysroot for candidate environment (required for stack-check mode)
search-path Additional library search directories (space-separated)
ld-library-path Simulated LD_LIBRARY_PATH (colon-separated)

Source-scan and build-source evidence (scan / dump / merge modes)

These inputs drive source intelligence — L3 build context, L4 source-ABI replay, and L5 source graphs — through the scan orchestrator, or fold the same evidence into a dump snapshot. L4/L5 need clang (installed automatically by install-deps: true); without it the scan degrades gracefully and L0–L2 stay authoritative.

Input Modes Description
sources scan, dump Source checkout/tree; drives L4 replay and graph collection. With a source-level depth and no compile DB, abicheck auto-detects the build system (CMake/Bazel) and runs the query itself to emit one — no flag, no manual build.
build-info scan, dump Out-of-tree L3 context: a build dir, a compile_commands.json, or a collected evidence pack.
compile-db scan (dump folds into build-info) Explicit compile_commands.json path.
build-config scan, dump Trusted .abicheck.yml; its build.query runs automatically (operator-supplied = trusted).
allow-build-query scan, dump Deprecated, ignored. Build queries now run automatically when sources is given; kept as a no-op for backward compatibility.
depth scan, dump Evidence-depth dial: binary, headers, build, source, or full. Maps to --depth. (scan can also derive this from the level inputs below.)
baseline scan Previous build's dump/library to compare against (or use abi-baseline to auto-fetch one).
scan-mode scan Fixed (L,S) preset: pr (default), pr-deep, baseline, audit.
source-method scan Precise S-axis: s0s6, or auto (risk-driven, opt-in).
since scan Focus the scan on files changed vs a git ref (e.g. origin/main).
changed-path scan Changed path(s) to focus on (space-separated; alternative to since).
budget scan Time guard (e.g. 15m). The step fails on overflow (verdict: BUDGET_OVERFLOW) — a budget never silently shrinks scope.
audit scan Single-build hygiene lint, no baseline (intra-version cross-source checks).
estimate scan Dry-run: print projected per-layer cost and scan nothing (always exits 0).
crosscheck scan Per-check severity overrides KEY=LEVEL (off/info/warning/error), space-separated. Promoting a check to =error makes a finding for it exit 2 (the API_BREAK tier); pair with fail-on-api-break: true to gate the step.
risk-rules scan Path to a YAML file overriding the risk_rules profile.
merge-inputs merge Space-separated .abi.json dumps and/or a Flow-2 abicheck_inputs/ pack directory to combine.
on-conflict merge warn (default, first-wins + diagnostic) or error (exit non-zero) when two inputs supply the same layer with differing facts.

format in scan mode

scan supports format: text (default) or json; other values fall back to text with a warning. merge writes a .abi.json baseline to output-file (default merged-baseline.json).

Output and policy

Input Default Description
format markdown (text for scan) Output format: markdown, json, sarif, html. sarif/html are compare-only; compare-release/appcompat/deps/stack-check fall back to markdown; scan supports only text/json and falls back to text.
output-file Path to write report (auto-set for SARIF)
policy strict_abi Built-in policy: strict_abi, sdk_vendor, plugin_abi
policy-file Custom YAML policy file
suppress YAML suppression file (supports label, source_location, expires)
verbose false Enable debug output

To enable suppression lifecycle enforcement, pass the flags via extra-args:

extra-args: '--strict-suppressions --require-justification'

Action behavior

Input Default Description
python-version 3.13 Python version for setup-python
install-deps true Install castxml + gcc automatically
upload-sarif false Upload SARIF to GitHub Code Scanning
fail-on-breaking true Fail step on binary ABI break
fail-on-api-break false Fail step on source-level API break
severity-preset Severity preset: default, strict, or info-only (compare mode only)
severity-addition Severity for additions: error, warning, or info (compare mode only)
extra-args '' Additional CLI arguments passed to abicheck
add-job-summary true Write summary to Job Summary panel (ignored for dump mode)
pr-comment true Post a sticky ABI report comment on the PR (compare/compare-release/appcompat). No-op outside pull_request events.
pr-comment-mode update update keeps one comment and edits it in place; new posts a fresh comment each run
pr-comment-on changes When to comment: changes, always, or never
pr-comment-detail standard Comment detail: summary, standard, or full
github-token ${{ github.token }} Token for the PR comment and baseline auto-fetch (needs pull-requests: write)

Package comparison inputs (compare-release mode)

Input Default Description
debug-info1 Debug info package for old side (RPM/Deb/tar)
debug-info2 Debug info package for new side (RPM/Deb/tar)
devel-pkg1 Development package with headers for old side
devel-pkg2 Development package with headers for new side
dso-only false Only compare shared objects, skip executables
include-private-dso false Include private (non-public) shared objects
keep-extracted false Keep extracted temp files for debugging
fail-on-removed-library false Exit 8 when a library present in old is absent in new

Outputs

Output Description
verdict compare: COMPATIBLE, SEVERITY_ERROR, API_BREAK, BREAKING, or ERROR. compare-release: COMPATIBLE, API_BREAK, BREAKING, REMOVED_LIBRARY, or ERROR. appcompat: COMPATIBLE, API_BREAK, BREAKING, or ERROR. dump: COMPATIBLE or ERROR. scan: COMPATIBLE, API_BREAK, BREAKING, BUDGET_OVERFLOW, or ERROR. merge: COMPATIBLE or ERROR. stack-check: PASS, WARN, FAIL, or ERROR. deps: PASS, FAIL, or ERROR.
exit-code compare: 0 (compatible), 1 (severity error), 2 (API break), 4 (ABI break). compare-release: 0 (compatible), 2 (API break), 4 (ABI break), 8 (library removed). appcompat: 0 (compatible), 2 (API break), 4 (ABI break). scan: 0 (compatible/advisory), 2 (API break), 4 (ABI break), 5 (budget overflow). merge: 0 (ok). stack-check: 0 (pass), 1 (warn), 4 (fail). deps: 0 (ok), 1 (missing).
report-path Path to the generated report file (empty when no output file was produced)

Usage examples

Compare two libraries on a PR

name: ABI Check
on: [pull_request]

jobs:
  abi-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build library
        run: mkdir build && cd build && cmake .. && make

      - name: Check ABI compatibility
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: abi-baseline.json  # committed to repo
          new-library: build/libfoo.so
          new-header: include/foo.h
          new-version: pr-${{ github.event.pull_request.number }}

Save a baseline on release

The baseline is a JSON snapshot of the library's ABI surface. Generate it when you release a version, then compare against it on every PR.

name: ABI Baseline
on:
  release:
    types: [published]

jobs:
  save-baseline:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build library
        run: mkdir build && cd build && cmake .. && make

      - name: Dump ABI baseline
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: dump
          new-library: build/libfoo.so
          header: include/foo.h
          new-version: ${{ github.ref_name }}
          output-file: abi-baseline.json

      - name: Upload baseline as release asset
        uses: softprops/action-gh-release@v2
        with:
          files: abi-baseline.json

Download baseline and compare on PR

      - name: Download baseline from latest release
        run: gh release download --pattern 'abi-baseline.json' --dir .
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Check ABI
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: abi-baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h

Source scans (build & source evidence)

mode: scan is the one-step entry point for source intelligence. It classifies the PR's changed paths, always runs the compiler-free pattern and intra-version cross-source checks, then runs the pinned evidence level (L3 build context / L4 source-ABI replay / L5 source graph) and — when a baseline is given — compares against it. It emits a single coverage-annotated report saying, per layer, what ran versus what was skipped.

The common case needs four inputs — the built binary, its public headers, the source tree, and a baseline:

permissions:
  contents: read
jobs:
  abi-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # needed for `since: origin/...` change focusing

      - name: Build
        run: cmake -B build -S . && cmake --build build

      - name: Source-intelligence scan
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: scan
          new-library: build/libfoo.so
          new-header: include/
          sources: .
          baseline: abi-baseline.json   # committed, or use abi-baseline: latest-release
          since: origin/${{ github.base_ref }}   # focus on changed files
          fail-on-api-break: true       # gate on source/API breaks too

clang is installed automatically (for L4/L5). On a pull_request run, since: origin/${{ github.base_ref }} focuses the (expensive) source replay on the files the PR touched — pair it with fetch-depth: 0 in checkout so the base ref is available.

Pin the scan depth

By default scan runs the pr preset. Pin a precise level for reproducible CI with source-method (S-axis) or the coarser depth (L-axis):

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: scan
          new-library: build/libfoo.so
          new-header: include/
          sources: .
          baseline: abi-baseline.json
          source-method: s5     # full source-ABI replay (deterministic)
          budget: 15m           # fail (BUDGET_OVERFLOW) rather than overrun
Want… Set
Cheap build-flag drift only (L3) depth: build
Source semantics on changed TUs source-method: s4 + since:
Full source-ABI replay source-method: s5
Source graph / localization (L5) source-method: s4 or scan-mode: pr-deep
Risk-driven (dev/local, opt-in) source-method: auto + since:

Single-release audit (no baseline)

Run the intra-version hygiene checks against one build — no old version needed. Useful as a standing lint on the default branch:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: scan
          new-library: build/libfoo.so
          new-header: include/
          sources: .
          audit: 'true'

Estimate cost before committing to a depth

estimate: 'true' is a dry run — it prints the projected per-layer cost (TU count, seconds) and scans nothing, always exiting 0. Handy when sizing a budget for a large repo:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: scan
          new-library: build/libfoo.so
          new-header: include/
          sources: .
          source-method: s5
          estimate: 'true'

Gate CI on a specific cross-source check

Cross-source findings are advisory by default. Promoting one to error makes a finding for it exit 2 (the API_BREAK tier); add fail-on-api-break: true so that exit turns the step red:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: scan
          new-library: build/libfoo.so
          new-header: include/
          sources: .
          baseline: abi-baseline.json
          crosscheck: 'private_header_leak=error odr_type_variant=error'
          fail-on-api-break: true   # gate on the exit-2 (API_BREAK) tier

fail-on-api-break gates the whole API_BREAK tier (baseline/source breaks and promoted cross-checks alike); the Action can't tell from the exit code which one fired, so leave it false if you only want binary ABI breaks (exit 4) to gate.

Passing sources into a baseline (build/source evidence)

There are three ways to feed L3/L4/L5 evidence into the comparison. Pick by where your build produces facts.

A. Inline at dump time (simplest)

dump with sources/build-info embeds the build/source facts inline in the snapshot, so any later compare (including one run from this Action on two such snapshots) carries the L3/L4/L5 findings — no out-of-band directories:

      - name: Dump baseline with build + source evidence
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: dump
          new-library: build/libfoo.so
          header: include/
          sources: .
          depth: source                 # full L3+L4+L5 for a baseline
          output-file: abi-baseline.json

Compare two such snapshots later with the default compare mode — the embedded evidence diffs automatically.

B. Combine independently-produced dumps with merge

When the binary side and source side are produced in parallel (e.g. on different runners), mode: merge folds them into one self-contained baseline:

      # one job produces the artifact-side dump (L0/L1/L2)…
      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: dump
          new-library: build/libfoo.so
          header: include/
          output-file: libfoo.bin.json

      # …another produces the source-side dump (L3/L4/L5)…
      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: dump
          sources: ./libfoo-src/
          output-file: libfoo.src.json

      # …then merge them into one baseline:
      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: merge
          merge-inputs: 'libfoo.bin.json libfoo.src.json'
          on-conflict: error            # good for baseline generation
          output-file: libfoo.baseline.json

C. Build-emitted facts (Flow-2 abicheck_inputs/ pack)

A product build that emits a self-describing abicheck_inputs/ pack (via abicheck-cc — see Build Info & Sources) needs no source replay in CI. mode: merge ingests the pack directly:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: merge
          merge-inputs: 'libfoo.bin.json ./abicheck_inputs/'
          output-file: libfoo.baseline.json

The resulting libfoo.baseline.json is a normal snapshot — pass it as old-library/baseline to any later compare or scan.

Use GitHub Actions cache for baseline

      - name: Restore cached baseline
        uses: actions/cache@v4
        with:
          path: abi-baseline.json
          key: abi-baseline-${{ github.event.repository.default_branch }}-${{ github.sha }}
          restore-keys: |
            abi-baseline-${{ github.event.repository.default_branch }}-

      - name: Check ABI
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: abi-baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h

SARIF with GitHub Code Scanning

Upload results to the Security tab so ABI breaks appear as code scanning alerts.

Note

Requires security-events: write permission. On PRs, GitHub only shows new alerts introduced by the PR — existing alerts stay on the default branch and don't clutter the review.

jobs:
  abi-check:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - run: mkdir build && cd build && cmake .. && make

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: abi-baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          format: sarif
          upload-sarif: true

Cross-compilation check (dump mode)

Cross-compilation flags (gcc-prefix, sysroot, gcc-options) are only supported in dump mode. Use mode: dump to generate a baseline from a cross-compiled binary, then compare with a separate step.

      # Step 1: dump ABI snapshot from cross-compiled binary
      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: dump
          new-library: build-arm64/libfoo.so
          header: include/foo.h
          gcc-prefix: aarch64-linux-gnu-
          sysroot: /usr/aarch64-linux-gnu
          lang: c
          output-file: baseline-arm64.json

Matrix: multiple libraries

    strategy:
      matrix:
        lib:
          - { name: libfoo, so: build/libfoo.so, header: include/foo.h }
          - { name: libbar, so: build/libbar.so, header: include/bar.h }
    steps:
      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baselines/${{ matrix.lib.name }}.json
          new-library: ${{ matrix.lib.so }}
          new-header: ${{ matrix.lib.header }}

Matrix: multiple platforms (native scan per OS)

Use native runners to get the best platform-specific signal (Linux/ELF, macOS/Mach-O, Windows/PE):

jobs:
  abi-scan:
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            ext: so
          - os: macos-latest
            ext: dylib
          - os: windows-latest
            ext: dll
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      # Build your platform artifact here (example command only)
      - name: Build
        run: |
          echo "build on ${{ matrix.os }}"

      - name: ABI compare (native)
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baselines/${{ runner.os }}/abi-old.json
          new-library: build/${{ runner.os }}/libfoo.${{ matrix.ext }}
          new-header: include/foo.h
          format: json
          output-file: abi-report-${{ runner.os }}.json

      - name: Upload platform ABI report
        uses: actions/upload-artifact@v4
        with:
          name: abi-report-${{ runner.os }}
          path: abi-report-${{ runner.os }}.json

Post-matrix ABI gate (unified verdict)

After per-platform matrix runs, a gate job downloads all JSON reports and produces one aggregated exit code for the entire workflow:

jobs:
  abi-scan:
    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            ext: so
          - os: macos-latest
            ext: dylib
          - os: windows-latest
            ext: dll
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: cmake -B build && cmake --build build

      - name: ABI compare (native)
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baselines/${{ runner.os }}/abi-old.json
          new-library: build/libfoo.${{ matrix.ext }}
          new-header: include/foo.h
          format: json
          output-file: abi-report-${{ runner.os }}.json
          fail-on-breaking: false   # let gate job decide

      - name: Upload platform ABI report
        uses: actions/upload-artifact@v4
        with:
          name: abi-report-${{ runner.os }}
          path: abi-report-${{ runner.os }}.json

  abi-gate:
    needs: abi-scan
    runs-on: ubuntu-latest
    steps:
      - name: Download all ABI reports
        uses: actions/download-artifact@v4
        with:
          pattern: abi-report-*
          merge-multiple: true
          path: abi-reports/

      - name: Aggregate verdicts and gate
        run: |
          pip install abicheck --quiet
          python3 - <<'PYEOF'
          import json, sys, os, glob

          SEVERITY = {"NO_CHANGE": 0, "COMPATIBLE": 0, "COMPATIBLE_WITH_RISK": 0,
                      "API_BREAK": 2, "BREAKING": 4, "ERROR": 4}

          worst = 0
          rows = []
          for path in sorted(glob.glob("abi-reports/*.json")):
              with open(path) as f:
                  data = json.load(f)
              verdict = data.get("verdict", "ERROR")
              platform = os.path.basename(path).replace("abi-report-", "").replace(".json", "")
              rows.append(f"| {platform} | {verdict} |")
              worst = max(worst, SEVERITY.get(verdict, 4))

          table = "\n".join(rows)
          print(f"## ABI Gate\n\n| Platform | Verdict |\n|---|---|\n{table}")

          if worst >= 4:
              print("BREAKING ABI change detected on at least one platform.", file=sys.stderr)
              sys.exit(4)
          elif worst >= 2:
              print("API break detected on at least one platform.", file=sys.stderr)
              sys.exit(2)
          print("All platforms: compatible.")
          PYEOF

Tip

Set fail-on-breaking: false in each matrix job so runners don't fail early. The gate job reads all JSON reports and exits 4 (breaking), 2 (API break), or 0 (compatible).

Skip system dependency installation

If castxml + compiler are already available (custom image, pre-provisioned VM, or conda-forge environment), set install-deps: false:

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: old.json
          new-library: new.json
          install-deps: false

Example (conda-forge pre-step):

      - name: Install abicheck from conda-forge
        run: |
          conda install -y -c conda-forge abicheck

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: old.json
          new-library: new.json
          install-deps: false

When comparing two JSON snapshots, no header-analysis toolchain is needed.

Full-stack dependency check on container image update

Validate that updating a base image doesn't break your application's dependency stack. This runs stack-check to compare the binary's full transitive dependency tree across old and new container root filesystems:

jobs:
  stack-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Extract old rootfs
        run: |
          mkdir -p /tmp/old-root
          docker export $(docker create old-image:latest) | tar -xf - -C /tmp/old-root

      - name: Extract new rootfs
        run: |
          mkdir -p /tmp/new-root
          docker export $(docker create new-image:latest) | tar -xf - -C /tmp/new-root

      - name: Full-stack ABI check
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: stack-check
          new-library: usr/bin/myapp
          baseline: /tmp/old-root
          candidate: /tmp/new-root
          format: json
          output-file: stack-report.json

Exit codes for stack-check: 0 = PASS, 1 = WARN (ABI risk), 4 = FAIL (load failure or ABI break).

Dependency tree audit

Show the resolved dependency tree and symbol binding status for a binary. Useful for auditing which libraries a binary actually loads and detecting missing dependencies before deployment:

      - name: Audit dependencies
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: deps
          new-library: build/myapp
          sysroot: /path/to/target/rootfs

Include dependency info in compare

Add follow-deps: true to include the transitive dependency graph and symbol binding information alongside the regular ABI diff:

      - name: Compare with dependency context
        uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          follow-deps: true

Inline PR annotations

Add --annotate to get ABI breaking changes as inline comments on the PR diff. See GitHub PR Annotations for full details.

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          extra-args: --annotate

Sticky PR comment

On pull_request runs the action posts a single, self-updating comment that groups every finding into Breaking, Needs review, and Safe sections and shows the scanned head SHA. It is a content channel only — it never changes the check's red/green state, which is still driven by fail-on-breaking / fail-on-api-break / severity-*. This means review-needed items (source breaks, risk, additions) surface as a green check with a ⚠️ Review recommended comment, while real ABI breaks turn the check red and post a comment.

permissions:
  contents: read
  pull-requests: write   # required for the comment
jobs:
  abi:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          # all optional — these are the defaults:
          pr-comment: true
          pr-comment-mode: update      # one sticky comment, edited each run
          pr-comment-on: changes       # skip the comment when nothing changed
          pr-comment-detail: standard  # per-symbol tables for breaking/review

Behavior knobs:

  • pr-comment-mode: new posts a fresh comment per run instead of editing the previous one (use when you want a per-commit history in the thread).
  • pr-comment-on: always comments every run, including a clean No ABI changes result; never disables it.
  • pr-comment-detail: full lists every change with source locations and expands all sections; summary reduces the comment to the verdict and counts.

On large diffs the standard view stays readable by rolling related changes up to their enclosing API — overloads, template instantiations and members of the same type/namespace collapse into one row showing the family and a member count (distinct symbols keep their own row; full keeps every change separate). The body is always kept under GitHub's 65,536-character comment limit: if it would overflow, the detail level is automatically reduced (and, as a last resort, the body is truncated), with a link back to the full report uploaded as the workflow-run artifact so nothing is lost.

"Safe" mirrors whatever the checker already classified as compatible — so public-header surface scoping (--scope-public-headers) and policy profiles (e.g. sdk_vendor demoting a removal) flow through automatically; the comment never re-classifies anything.

The comment also tracks the gate: with fail-on-api-break: true (which turns the check red on source/API breaks), those findings are filed under Breaking in the comment to match, rather than Needs review.

Conditional failure

Allow API breaks but block binary ABI breaks:

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          fail-on-breaking: true
          fail-on-api-break: false

Detect unintentional API expansion

Block PRs that accidentally add new public symbols or types:

      - uses: abicheck/abicheck@v0.3.0
        with:
          old-library: baseline.json
          new-library: build/libfoo.so
          new-header: include/foo.h
          fail-on-breaking: true
          severity-addition: error   # exit code 1 if any new public API appears

When severity-addition: error: - Exit code 1 → new public symbol/type added (verdict: SEVERITY_ERROR) - Exit code 0 → no additions, no breaks (verdict: COMPATIBLE) - Exit code 4 → binary ABI break (verdict: BREAKING)

This is useful when your library has a stable frozen API and any expansion must be a deliberate, reviewed decision rather than an accidental side effect.

Compare RPM packages

Use mode: compare-release to compare all shared libraries inside two packages without manual extraction. Supported formats: RPM, Deb, tar (.tar.gz, .tar.xz, .tar.bz2, .tgz), conda (.conda, .tar.bz2), wheel (.whl), and plain directories.

      - name: Compare RPM packages
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: compare-release
          old-library: libfoo-1.0-1.el9.x86_64.rpm
          new-library: libfoo-1.1-1.el9.x86_64.rpm

Compare packages with debug info

Provide separate debug info packages for full type-level analysis via build-id resolution:

      - name: Compare with debug info
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: compare-release
          old-library: libfoo-1.0.rpm
          new-library: libfoo-1.1.rpm
          debug-info1: libfoo-debuginfo-1.0.rpm
          debug-info2: libfoo-debuginfo-1.1.rpm

Compare Deb packages with development headers

      - name: Compare Deb packages
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: compare-release
          old-library: libfoo1_1.0-1_amd64.deb
          new-library: libfoo1_1.1-1_amd64.deb
          devel-pkg1: libfoo-dev_1.0-1_amd64.deb
          devel-pkg2: libfoo-dev_1.1-1_amd64.deb

Compare tar archives (DSOs only)

      - name: Compare SDK tarballs
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: compare-release
          old-library: sdk-2.0.tar.gz
          new-library: sdk-2.1.tar.gz
          dso-only: true

Compare conda packages

      - name: Compare conda packages
        uses: abicheck/abicheck@v0.3.0
        with:
          mode: compare-release
          old-library: pkg-v1.conda
          new-library: pkg-v2.conda

Application compatibility check

Check whether your application binary is affected by a library update:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: appcompat
          app-binary: build/myapp
          old-library: libfoo.so.1
          new-library: build/libfoo.so.2
          header: include/foo.h

Quick symbol availability check (weak mode)

Verify a library provides all symbols an application needs — no old library required:

      - uses: abicheck/abicheck@v0.3.0
        with:
          mode: appcompat
          app-binary: build/myapp
          check-against: build/libfoo.so
          install-deps: false

Versioning

The action follows semantic versioning. While abicheck is pre-1.0, pin an exact release tag (the examples in this guide use the latest, v0.3.0); a floating major tag is not published yet:

uses: abicheck/abicheck@v0.3.0     # exact release tag (recommended, reproducible)
uses: abicheck/abicheck@abc123def  # exact commit SHA (most secure)

Released tags are listed on the Releases page. Once abicheck reaches a stable 1.0, a floating v1 major tag updated on each patch/minor release will become the recommended pin.