Use when a pull request changes .proto files for gRPC services, Buf Schema Registry modules or message-bus payloads, and consumers must not break. Runs buf breaking against a past version, picks the FILE, PACKAGE, WIRE_JSON or WIRE category, sets exemptions and gates CI.
90%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
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* packagesversion: 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 binaryUseful 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.