Detect breaking changes locally
buf breaking compares a set of .proto files against a previous version and reports any changes that would break generated code or existing serialized data. This walkthrough runs buf breaking against three common baselines: uncommitted edits in a local Git checkout, the current version of a module on the BSR, and the latest commit of a remote GitHub repository. It also shows how switching the rule set changes what counts as breaking.
For what breaking change detection is for, how the editor integration surfaces errors, and how breaking checks fit into code review, see the overview.
Prerequisites
Install the Buf CLI.
Clone the examples repository and enter the quickstart’s
startdirectory:shgit clone git@github.com:bufbuild/buf-examples.git cd buf-examples/cli/breaking-change-detection/start
Edit files inside the start directory as you follow along. A sibling finish directory shows the final state, in case you want to compare at any point.
Workspace layout
buf.yaml sits at the workspace root and declares a single module under proto/:
cli/breaking-change-detection/start/
.
├── buf.yaml
└── proto
└── pet
└── v1
└── pet.protobreaking.use in buf.yaml controls which breaking change rules apply. It starts at FILE, Buf’s recommended default, which catches anything that would break wire or source compatibility at the level of individual files.
cli/breaking-change-detection/start/buf.yaml
version: v2
modules:
- path: proto
name: buf.build/tutorials/breaking
lint:
use:
- STANDARD
breaking:
use:
- FILESee the buf.yaml reference for every field, and the rules page for what each rule set covers.
Compare against a local Git checkout
Your clone is a Git working tree, so buf breaking can compare uncommitted edits against HEAD.
Add a new pet type to the PetType enum in pet.proto:
cli/breaking-change-detection/start/proto/pet/v1/pet.proto
// PetType represents the different types of pets in the pet store.
enum PetType {
PET_TYPE_UNSPECIFIED = 0;
PET_TYPE_CAT = 1;
PET_TYPE_DOG = 2;
PET_TYPE_SNAKE = 3;
PET_TYPE_HAMSTER = 4;
PET_TYPE_BIRD = 5;
}Appending a new enum value is non-breaking, so the command exits cleanly:
cli/breaking-change-detection/start/
buf breaking --against '../../../.git#subdir=cli/breaking-change-detection/start/proto'--against here targets the .git directory at the repository root, and subdir= selects the module inside it. See the inputs reference for the full Git input grammar, including other refs like branch=, tag=, and ref=.
Compare against the BSR
When a module lives on the BSR, the most common baseline is the latest published version. This quickstart’s module is published at buf.build/tutorials/breaking, which is what the name field in buf.yaml already points to.
Make a breaking change by renaming PET_TYPE_HAMSTER:
cli/breaking-change-detection/start/proto/pet/v1/pet.proto
// PetType represents the different types of pets in the pet store.
enum PetType {
PET_TYPE_UNSPECIFIED = 0;
PET_TYPE_CAT = 1;
PET_TYPE_DOG = 2;
PET_TYPE_SNAKE = 3;
PET_TYPE_HAMSTER = 4;
PET_TYPE_RODENT = 4;
PET_TYPE_BIRD = 5;
}Run buf breaking against the BSR module directly:
cli/breaking-change-detection/start/
buf breaking --against buf.build/tutorials/breaking
proto/pet/v1/pet.proto:11:21:Enum value "4" on enum "PetType" changed name from "PET_TYPE_HAMSTER" to "PET_TYPE_RODENT".Revert the rename before moving on.
Compare against a remote Git repository
If a module isn’t on the BSR, the remote Git branch is usually the baseline to compare against. This step also exercises the configuration: moving the enum to its own file is a breaking change under FILE, but not under PACKAGE.
Create pet_type.proto alongside pet.proto:
cli/breaking-change-detection/start/
touch proto/pet/v1/pet_type.protoPaste the enum into it, keeping the package as pet.v1:
cli/breaking-change-detection/start/proto/pet/v1/pet_type.proto
syntax = "proto3";
package pet.v1;
// PetType represents the different types of pets in the pet store.
enum PetType {
PET_TYPE_UNSPECIFIED = 0;
PET_TYPE_CAT = 1;
PET_TYPE_DOG = 2;
PET_TYPE_SNAKE = 3;
PET_TYPE_HAMSTER = 4;
}In pet.proto, remove the enum and add an import so any remaining references resolve to the new file:
cli/breaking-change-detection/start/proto/pet/v1/pet.proto
syntax = "proto3";
package pet.v1;
import "pet/v1/pet_type.proto";
// PetType represents the different types of pets in the pet store.
enum PetType {
PET_TYPE_UNSPECIFIED = 0;
PET_TYPE_CAT = 1;
PET_TYPE_DOG = 2;
PET_TYPE_SNAKE = 3;
PET_TYPE_HAMSTER = 4;
}Leave any other content (syntax declaration, package, messages) in place.
Run buf breaking against the main branch of the remote repository:
cli/breaking-change-detection/start/
buf breaking --against 'https://github.com/bufbuild/buf-examples.git#branch=main,subdir=cli/breaking-change-detection/start/proto'
proto/pet/v1/pet.proto:1:1:Previously present enum "PetType" was deleted from file.Under FILE, moving PetType out of pet.proto reads as a deletion, even though the enum still exists in the same package.
Relax the rule set
If your team treats moves within a package as non-breaking, switch to the PACKAGE rule set, which checks compatibility at the package level rather than per file:
cli/breaking-change-detection/start/buf.yaml
breaking:
use:
- FILE
- PACKAGERun the same command again and the error is gone:
cli/breaking-change-detection/start/
buf breaking --against 'https://github.com/bufbuild/buf-examples.git#branch=main,subdir=cli/breaking-change-detection/start/proto'See the rules page for every rule set, category, and individual rule, including the trade-offs between FILE, PACKAGE, and WIRE.
Next steps
- Breaking change rules: the full catalog of rules, grouped by rule set and category.
- BSR breaking check: run the same detection on every push to the registry, enforced from the server side independent of local configuration.
- Inputs reference: the complete grammar for
--against, including Git refs, tarballs, zip archives, and BSR modules.