protobuf-compat-checking
Wraps `buf breaking` to compare a Protobuf schema against a past version, classifies breaking changes by category (FILE / PACKAGE / WIRE_JSON / WIRE), configures buf.yaml rule selection plus per-path exemptions, and gates CI on the exit code. Use when reviewing `.proto` changes in a PR for gRPC services, BSR modules, or schema-versioned message bus payloads.
Install with skills.sh (any agent)
npx skills add testland/qa --skill protobuf-compat-checkingprotobuf-compat-checking
Overview
buf breaking is the canonical Protobuf compatibility checker from the Buf project (buf-breaking (opens in new window)). It compares the current schema against a past version (BSR module, Git ref, tarball, or buf image) and reports changes that would break clients, servers, or generated code.
This is the Protobuf-specific counterpart to openapi-contract-diff (REST) and graphql-schema-regression (GraphQL).
When to use
Breaking categories
buf breaking rules ship in four nested categories - stricter subsumes looser (buf-breaking (opens in new window)):
| Category | Detects |
|---|---|
FILE | Source-code breakage per-file. Default; strictest. |
PACKAGE | Source-code breakage per-package. |
WIRE_JSON | Wire-format or JSON-encoding breakage. |
WIRE | Wire-format breakage only. Loosest. |
Choose FILE for new internal services where source compatibility matters; choose WIRE_JSON for stable APIs where source rearrangement (e.g. moving a message between files) is acceptable but JSON-mapping breakage is not; choose WIRE only when JSON encoding is out-of-scope.
Configuration
buf.yaml v2 form (buf-yaml (opens in new window)):
version: v2
modules:
- path: proto
name: buf.build/acme/foo
breaking:
use:
- FILE
except:
- FILE_NO_DELETE # allow file deletions despite using FILE
ignore:
- foo/legacy/ # exclude entire path from all breaking rules
ignore_only:
FILE_NO_DELETE:
- foo/bar.proto # exclude specific path from a specific rule
ignore_unstable_packages: true # ignore v1alpha1 / v1beta1 / *test* packagesversion: v2 is required for the structure above; legacy v1 and v1beta1 files use a different shape (buf-yaml (opens in new window)). When migrating, run buf migrate v1tov2 to convert.
ignore_unstable_packages: true is the canonical pattern - Protobuf convention is that v1alpha1, v1beta1, and *test* packages are unstable and explicitly NOT covered by compatibility guarantees.
Running
Against a Git reference
buf breaking --against '.git#branch=main'
buf breaking --against '.git#tag=v1.2.0'
buf breaking --against '.git#ref=HEAD~1'(Per buf-breaking (opens in new window).)
The .git#... syntax tells buf to checkout the referenced ref into a temp directory and use it as the baseline. This is the most common CI pattern: compare PR against main.
Against a BSR module
buf breaking --against buf.build/acme/foo
buf breaking --against buf.build/acme/foo:<commit-sha>Use the BSR baseline when the team treats BSR as the source of truth and a release labeled v1 is what production is consuming.
Against a tarball or buf image
buf breaking --against archive.tar.gz
buf breaking --against image.bin # buf-image-format binaryUseful for hermetic builds where the baseline is published as an artifact rather than a git ref.
Reading the report
buf breaking exits non-zero on detected breaking changes. The default output prints one line per finding with the offending file, line, column, rule ID, and message:
proto/orders/v1/order.proto:42:3:Field "1" with name "amount" on message
"Order" has the same number as deleted field with name "total".
[ENUM_VALUE_NO_DELETE_RESERVED]For machine consumption, supply --error-format json:
buf breaking --against '.git#branch=main' --error-format json{
"path": "proto/orders/v1/order.proto",
"start_line": 42,
"start_column": 3,
"type": "ENUM_VALUE_NO_DELETE_RESERVED",
"message": "Field 1 ..."
}Pipe to jq for triage:
buf breaking --against '.git#branch=main' --error-format json | \
jq -r '"\(.type) \(.path):\(.start_line) - \(.message)"'CI integration
GitHub Actions
The Buf project ships an official Action (bufbuild/buf-action) that runs lint + breaking + push against BSR in one job. For self-hosted breaking-only:
# .github/workflows/buf-breaking.yml
name: buf-breaking
on:
pull_request:
paths:
- '**/*.proto'
- 'buf.yaml'
- 'buf.lock'
jobs:
breaking:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0 # need history for `.git#branch=main` baseline
- uses: bufbuild/buf-setup-action@v1
- name: buf breaking
run: buf breaking --against '.git#branch=main,subdir=.'fetch-depth: 0 is required so the .git#branch=main ref resolves - the default shallow clone doesn't have it.
For stricter pipelines, also gate on buf lint:
- name: buf lint
run: buf lint
- name: buf breaking
run: buf breaking --against '.git#branch=main,subdir=.'Tag-based releases
For projects using git tags as release boundaries:
# Compare PR vs the latest released tag
LATEST_TAG=$(git describe --tags --abbrev=0)
buf breaking --against ".git#tag=${LATEST_TAG}"This catches breaking changes vs production, not just vs the trunk HEAD - useful when main itself contains in-progress un-released work.
References
Related skills
contract-compatibility-gate
Builds a unified deployment-readiness gate that aggregates verdicts from any combination of Pact `can-i-deploy`, oasdiff (OpenAPI), graphql-inspector (GraphQL), and `buf breaking` (Protobuf), applies severity-aware pass/fail thresholds, and emits a single go / no-go decision with per-finding rationale. Use when authoring a CI step that gates a deployment on cross-protocol contract compatibility.
graphql-schema-regression
Diffs two GraphQL schemas with `graphql-inspector diff`, classifies each change as BREAKING / DANGEROUS / NON_BREAKING, and applies opt-in rules (suppress-removal-of-deprecated-field, consider-usage, ignore-description-changes) to gate CI on schema regressions. Use when reviewing GraphQL schema changes in a PR or evaluating Federation supergraph drift.
openapi-contract-diff
Diffs two OpenAPI versions for breaking changes (removed endpoints, deleted schemas, narrowed enums, response shape changes) using `oasdiff breaking`, severity-classifies the findings (ERR / WARN / INFO), and gates CI with `--fail-on`. Use when reviewing an OpenAPI spec change in a PR or on a release tag.
pact-contract-testing
Authors and verifies Pact consumer-driven contract tests across the full Pact lifecycle - consumer tests producing pact files, publishing to the Pact Broker, provider verification, and `can-i-deploy` deployment gates. Use when introducing a new HTTP/JSON API contract between two services, diagnosing breaking changes, or wiring contract verification into CI.