From qa-contract-testing
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-contract-testing:protobuf-compat-checkingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`buf breaking` is the canonical Protobuf compatibility checker from
buf breaking is the canonical Protobuf compatibility checker from
the Buf project (buf-breaking). 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).
.proto files (gRPC service definitions, message-bus
schemas, internal data contracts).buf breaking rules ship in four nested categories - stricter
subsumes looser (buf-breaking):
| 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.
buf.yaml v2 form (buf-yaml):
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* packages
version: v2 is required for the structure above; legacy v1 and
v1beta1 files use a different shape (buf-yaml). 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.
buf breaking --against '.git#branch=main'
buf breaking --against '.git#tag=v1.2.0'
buf breaking --against '.git#ref=HEAD~1'
(Per buf-breaking.)
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.
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.
buf breaking --against archive.tar.gz
buf breaking --against image.bin # buf-image-format binary
Useful for hermetic builds where the baseline is published as an artifact rather than a git ref.
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)"'
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=.'
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.
buf breaking semantics, categories,
baseline reference formats.buf.yaml configuration; breaking.use,
except, ignore, ignore_only, ignore_unstable_packages.openapi-contract-diff - REST
counterpart.graphql-schema-regression - GraphQL counterpart.contract-compatibility-gate - gate skill aggregating breaking-change verdicts across protocols.npx claudepluginhub testland/qa --plugin qa-contract-testingProvides a checklist for code reviews covering functionality, security, performance, maintainability, tests, and quality. Use for pull requests, audits, team standards, and developer training.