buf breaking: Detect Breaking Proto Changes in CI
buf breaking diffs the current proto module against a baseline (--against) and exits non-zero when a change breaks wire or source compatibility.
Schema changes that look harmless can break deployed clients. buf breaking compares against a git ref or image and names the exact field or message that broke.
What it does
buf breaking compiles both the current input and the --against baseline, then checks each enabled breaking rule (categories FILE, PACKAGE, WIRE, WIRE_JSON). FILE is the strictest (source compatibility); WIRE only flags on-the-wire breakage. It reports each incompatibility with the file and field involved.
Common usage
# compare working tree against main on origin
buf breaking --against '.git#branch=main'
# compare against the previous commit
buf breaking --against '.git#ref=HEAD~1'
# compare against a prebuilt image
buf breaking --against image.binpb
# buf.yaml
# breaking:
# use:
# - FILEFlags and options
| Flag / config | What it does |
|---|---|
| --against <input> | Baseline: git ref, image file, dir, or tarball (required) |
| --path <path> | Restrict the comparison to specific files |
| --error-format <fmt> | text, json, msvs, or github-actions |
| breaking.use | Rule categories: FILE, PACKAGE, WIRE, WIRE_JSON |
| breaking.except | Specific breaking rules to skip |
| --exclude-imports | Ignore changes in imported files |
In CI
On pull requests, run buf breaking --against ".git#branch=main". Fetch enough git history (fetch-depth: 0 in actions/checkout) or the baseline ref is unavailable. Pick FILE for libraries, WIRE for services where source-level churn is acceptable but the wire format must stay stable.
Common errors in CI
A real violation reads like "Field "1" on message "User" changed type from "string" to "int64"" or "Previously present field "2" ... was deleted". "Failure: ... .git#branch=main: ... could not resolve ref" means the baseline ref is not fetched; deepen the checkout. "Failure: <against>: no .proto files were targeted" means the baseline path or module root is wrong.