GitHub PR Annotations¶
abicheck can emit GitHub Actions workflow command annotations so that ABI breaking changes appear as inline comments directly on PR diffs. Errors, warnings, and notices are pinned to the exact file and line where the change was detected.
Quick start¶
Add --annotate to your existing abicheck step:
- name: Check ABI compatibility
uses: napetrov/abicheck@v1
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
extra-args: --annotate
That's it. On the next PR, any breaking change detected by abicheck will show up as a red error annotation on the changed file in the PR diff view, and a Markdown summary will appear in the Job Summary panel.
How it works¶
When both conditions are met:
- The
--annotateflag is passed, and - The environment variable
GITHUB_ACTIONS=trueis set (automatic on all GitHub Actions runners)
abicheck prints workflow command annotations to stderr so that the primary stdout payload (JSON, SARIF, HTML, Markdown) remains a clean, machine-parsable stream. GitHub Actions processes workflow commands from both stdout and stderr, so annotations work correctly on both channels.
If $GITHUB_STEP_SUMMARY is available (also automatic on GitHub Actions
runners), abicheck appends a full Markdown ABI report to the
Job Summary panel.
Severity mapping¶
| Change category | Annotation level | Annotation title prefix | Enabled by default |
|---|---|---|---|
| BREAKING (binary ABI incompatible) | ::error |
ABI Break: <kind> |
Yes |
| API_BREAK (source-level break) | ::warning |
API Break: <kind> |
Yes |
| COMPATIBLE_WITH_RISK (deployment risk) | ::warning |
Deployment Risk: <kind> |
Yes |
| COMPATIBLE (additions, quality issues) | ::notice |
ABI Addition: <kind> |
Only with --annotate-additions |
Example annotation output¶
::error file=include/foo.h,line=42,title=ABI Break%3A func_params_changed::Parameter 1 of foo::baz changed from int to long (binary incompatible)
::warning file=include/foo.h,line=15,title=API Break%3A enum_member_renamed::Enum member renamed: kOld -> kNew
::warning title=Deployment Risk%3A symbol_version_required_added::New GLIBC_2.34 version requirement added
::notice title=ABI Addition%3A func_added::Function foo::new_thing() was added to the public interface
CLI flags¶
Both compare and compare-release commands support these flags:
--annotate¶
Emit GitHub Actions workflow command annotations to stderr. Annotations appear
as inline comments on PR diffs. Only effective when GITHUB_ACTIONS=true.
When not running inside GitHub Actions, this flag is silently ignored — you can leave it in your CI config and run the same command locally without side effects.
--annotate-additions¶
Include additions and compatible changes as ::notice annotations. Off by
default because additions are typically informational and can be noisy.
Requires --annotate. Passing --annotate-additions without --annotate
produces an error:
Usage examples¶
Basic: annotate breaking changes on PRs¶
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: napetrov/abicheck@v1
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
extra-args: --annotate
Include additions as notices¶
- name: Check ABI compatibility
uses: napetrov/abicheck@v1
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
extra-args: --annotate --annotate-additions
Annotate a release comparison¶
- name: Compare RPM packages
uses: napetrov/abicheck@v1
with:
mode: compare-release
old-library: libfoo-1.0-1.el9.x86_64.rpm
new-library: libfoo-1.1-1.el9.x86_64.rpm
extra-args: --annotate
Combine with SARIF upload¶
Annotations and SARIF are complementary: annotations give immediate inline feedback on the PR diff, while SARIF populates the Security tab with persistent alerts.
- name: Check ABI compatibility
uses: napetrov/abicheck@v1
with:
old-library: abi-baseline.json
new-library: build/libfoo.so
new-header: include/foo.h
format: sarif
upload-sarif: true
extra-args: --annotate
Local CLI usage (no annotations emitted)¶
When running locally, --annotate is a no-op since GITHUB_ACTIONS is not
set:
# Annotations silently skipped (GITHUB_ACTIONS not set)
abicheck compare libfoo.so.1 libfoo.so.2 \
--old-header v1/foo.h --new-header v2/foo.h \
--annotate
# To test annotation output locally, set the env var:
GITHUB_ACTIONS=true abicheck compare libfoo.so.1 libfoo.so.2 \
--old-header v1/foo.h --new-header v2/foo.h \
--annotate
Behavior details¶
Source location¶
Annotations include file= and line= properties only when abicheck has
source location information for the change. This is available when:
- Headers are provided (
-H,--old-header,--new-header) - DWARF debug info is present in the binary
- BTF/CTF metadata is available
In symbols-only mode (no headers, no debug info), annotations are still emitted but without file/line — they appear as step-level annotations rather than inline on the diff.
Annotation limit¶
GitHub Actions caps visible annotations at approximately 50 per step. abicheck enforces this limit and sorts annotations by severity so the most important ones (errors first, then warnings, then notices) are always visible.
For compare-release, the 50-annotation budget is shared across all libraries
in the release. This ensures a single noisy library doesn't consume all
available annotation slots.
Message truncation¶
Annotation messages are truncated to 200 characters to stay within GitHub's
undocumented message length limits. Long descriptions end with ....
Job Summary¶
When $GITHUB_STEP_SUMMARY is available, abicheck automatically appends a
full Markdown ABI report to the Job Summary panel. This provides the complete
report alongside the inline annotations.
compare: writes the per-library Markdown reportcompare-release: writes the consolidated release summary (one entry, not per-library)
Special character escaping¶
Annotation property values (file, line, title) escape :, ,, %, \n,
and \r using GitHub's %-encoding. Message bodies escape %, \n, and
\r only (colons are safe in the message portion).
Comparison with other annotation methods¶
| Method | Inline on diff | Persistent | Setup |
|---|---|---|---|
--annotate (this feature) |
Yes | No (per-run) | Add one flag |
| SARIF + Code Scanning | Yes (Security tab) | Yes (alerts) | format: sarif + upload-sarif: true + permissions |
| Job Summary | No (separate panel) | No (per-run) | Automatic with --annotate |
| Markdown report (default) | No (log output) | No | Default behavior |
For most teams, --annotate provides the best signal-to-noise ratio with zero
configuration beyond the single flag.
Troubleshooting¶
Annotations not appearing¶
- Is
--annotateset? Checkextra-argsin your workflow YAML. - Running on GitHub Actions? Annotations require
GITHUB_ACTIONS=true. Self-hosted runners set this automatically. - Are there any changes? No annotations are emitted for
NO_CHANGEresults. - File path mismatch? Annotations with
file=are only shown inline when the file path matches a file changed in the PR. Step-level annotations (without file/line) always appear in the Actions log. - Hit the 50-annotation limit? If you have more than 50 issues, lower-severity
ones are dropped. Use
--format jsonor check the Job Summary for the complete list.
Annotations appear but not inline¶
This happens when source_location is not available (symbols-only mode). To
get inline annotations, provide headers (-H) or ensure DWARF debug info is
present in the binary.
Too many notice annotations¶
Use --annotate without --annotate-additions (the default). This limits
annotations to breaking changes and warnings only.