AI Coding Agents
Code Review
How to keep Protobuf changes backward compatible
September 26, 2026 - 4 minute read
AI Coding Agents
Code Review
September 26, 2026 - 4 minute read
To keep a Protobuf change backward compatible, never reuse or renumber a field, reserve the number and name of every deleted field, prefer adding new fields to changing existing ones, and test that old clients can read new messages and new clients can read old ones. Check JSON mapping separately, because a rename that is safe on the wire can still break JSON consumers.
Protobuf compatibility depends on schema history, not just whether the current .proto file compiles. A coding agent can rename a field, reuse a deleted number, or change generated code in ways that pass local tests while breaking a client on an older release. The review has to model mixed versions and rollback.
Google's Proto Best Practices advises teams not to reuse field numbers and to reserve numbers for deleted fields. Those rules give an agent concrete constraints for schema work.
Scope the task with the changed message, affected services, generated language targets, and supported client versions. Include the previous released schema and the command that regenerates code. A repository snapshot may omit a consumer maintained elsewhere, so list known external owners explicitly.
Ask the agent to classify each change as additive, behavior-changing, or removal-related. Adding a field is usually easier to roll out than changing the meaning of an existing field. A rename keeps the wire number but can still affect JSON names, reflection, generated APIs, and code that addresses fields by name.
The rules above give a rough classification for common edits. Check the official Protobuf guidance for anything outside this table.
| Change | Wire format | Watch for |
|---|---|---|
| Add a field with a new number | Usually the easiest to roll out | Old readers keep it as an unknown field |
| Rename a field | Same wire number | JSON names, reflection, generated APIs |
| Delete a field | Safe only with the number and name reserved | Code that still reads the field |
| Reuse a deleted number | Breaking | Old data decoded with a new meaning |
| Singular to repeated, or a type change | Check the official guidance | Language-specific conversions |
Record whether each consumer uses binary encoding, JSON mapping, or both. A wire-compatible edit can still break a boundary that depends on generated names.
Compile the old and new schemas with the supported toolchain. Serialize representative messages with the old generated client and read them with the new client. Then reverse the direction. Include unknown fields, default values, enum values, nested messages, and any JSON conversion used at system boundaries.
The two-way test has a simple shape. Both directions must pass for every supported pair of versions.
Inspect deletions carefully. Reserve the removed field number and name rather than making either available for new meaning. Do not change a field between singular and repeated forms or rely on a language-specific type conversion without checking the official compatibility guidance.
Generated files should match a clean regeneration from the declared compiler and plugin versions. Review the schema diff separately from generated noise. If the toolchain version changes, keep that upgrade distinct or explain why it is required for the schema change.
Deployments rarely update every producer and consumer at once. Record which side can ship first, how long mixed versions may coexist, and what happens during rollback. A compatibility test should run in CI for the supported version window rather than only during the initial migration.
The pull request should include the old and new schema references, regeneration command, tested language targets, cross-version results, and rollout order. That evidence lets service owners evaluate the contract without reconstructing it from a large generated diff.
Factory is a platform for running a software factory, where agents called Droids take work from a signal to a reviewed pull request. For Protobuf work, Factory runs the bounded compatibility task in CI and adds a reviewer that knows the repository's schema rules.
Run the compatibility task headless. Droid Exec runs one non-interactive pass and can write JSON output, which suits a machine-readable compatibility report. Use an isolated worktree and require regeneration to finish with no unrelated output changes.
Review against schema rules. Factory's automated code review runs on pull request events and posts inline comments. A review-guidelines skill can tell it to flag field number reuse, missing reserved entries, generated-file drift, and missing cross-version tests. The compiler and compatibility suite remain the deterministic gates.
Coordinate multi-service changes. Missions are built for larger efforts that benefit from upfront planning and milestones. A schema change that touches several producers and consumers can be split into milestones that follow the rollout order, with validation at each one.
Old and new clients can still read each other's messages. In practice that means adding fields with new numbers, never reusing a field number, and reserving the number and name of deleted fields.
A rename keeps the wire number, so binary encoding still works. It can still break JSON names, reflection, generated APIs, and code that addresses fields by name.
Google's Proto Best Practices advise against reusing field numbers. Reserving the number and name stops a later change from giving old data a new meaning.
Serialize representative messages with the old generated client and read them with the new one, then reverse the direction. Include unknown fields, defaults, enums, nested messages, and JSON conversion.
Factory automated code review can use a review-guidelines skill to flag field reuse, generated-file drift, and missing cross-version tests, and Droid Exec can run the compatibility check in CI.
Discuss your team’s software development, privacy, or deployment requirements.
Start building