GitHub / Release Practice
Semantic Versioning Is a Promise, Not a Formality
For small projects, getting “breaking” right matters. When you change behavior, communicate clearly and follow your own rules.
Semantic Versioning (SemVer) looks straightforward: MAJOR.MINOR.PATCH. In practice, the hardest part is deciding when a change is breaking. In a small project, breaking changes cost contributors time and confidence. They also generate extra triage work you don’t have time for. For a public API, SemVer is a promise: when a contract change requires action from users, communicate it clearly and choose the appropriate version increment.
The failure mode
You change an endpoint’s response shape from {type:"present"} to {status:"present"} and add a field. Tests in your project pass because you updated them. A user’s integration breaks silently because they were parsing the old field. You release as a minor bump. Issues pile up. SemVer wasn’t just a label; it was a promise you broke.
What I learned
Define “breaking” for your project — then honor it
For a public API, breaking means any change that requires user action. That includes renames, removals, default behavior changes, stricter validation, and shifts in documented semantics. For plugin contracts, it includes method signatures and return-object fields. Write this down in CONTRIBUTING.md or RELEASE.md.
Use PATCH for fixes, MINOR for additive changes, MAJOR for breaking
PATCH: bug fixes that don’t change behavior or contracts. MINOR: new additive features, fields, or optional paths that don’t require code changes from users. MAJOR: anything else. If in doubt, lean toward stricter classification — small projects benefit from conservative signaling.
Keep a changelog that’s useful, not performative
A good CHANGELOG.md lists new features, fixes, and breaking changes with clear “migration” steps. Link to PRs and discussions when helpful, but don’t drown readers in noise. Make breaking changes a dedicated section with bullet points and example code.
Changelog entry (example)
## [2.0.0] - 2026-02-15
### Breaking
- Plugin interface: `evaluate()` return now requires `score_delta`.
- API response: field `note` renamed to `message`.
- Migration:
- Plugins: provide a score delta when constructing the result.
- Clients: read the renamed message field.
### Added
- New synthetic evidence source: "activity_count".
### Fixed
- Corrected null-handling in JSON serialization for missing evidence.
Automate release notes and tags, but write the words yourself
Automated release workflows help: tag versions, publish notes, and attach artifacts. The content of the notes should be authored by you or a maintainer who understands the changes. Meaningful context beats auto-generated lists.
Communicate before you break
— deprecation notes and timelines If you plan a breaking change, announce it with a deprecation note. Add warnings to logs, include a deprecation section in the changelog, and post a short explanation in discussions. Offer a migration window (e.g., two minor releases) and stick to it.
Validate SemVer behavior with contract tests
Combine SemVer discipline with tests: run a suite that loads the previous minor version’s contracts against the new release where feasible. If tests fail because you changed behavior without bumping MAJOR, you’ll catch it before publishing.
Keep scope under control
— fewer breaking changes, batched when necessary Batch breaking changes into a single major release rather than sprinkling them across minors. This helps users plan upgrades and reduces your triage load. Once you ship a major, leave a quiet window before the next one.
Trade-offs and failure modes
Trade-offs
Strict SemVer vs development speed: Bumping MAJOR and writing migration notes take time. They also save user time and reduce issue volume.
Deprecation windows vs maintenance overhead: Supporting old paths gives contributors time to adapt. For small projects, choose short, firm windows.
Automated release processes vs human judgment: Automation speeds delivery; human-curated notes ensure clarity. Pair them.
Failure modes
“Silent breaking minor”: Change that requires user action shipped as MINOR. It erodes trust and increases support requirements.
“Vague changelog”: Readers can’t tell what changed. Solution: explicit “Breaking,” “Added,” “Fixed” sections with migration steps.
“SemVer theater”: You declare SemVer, then ignore it in practice. Users stop trusting your version signals. Solution: define rules and follow them.
Try it yourself
Write a short “What counts as breaking” policy tailored to your project and add it to CONTRIBUTING.md.
Adopt Keep a Changelog structure and commit to explicit migration notes for breaking changes.
Add a contract test suite that runs compatibility checks against the previous minor version. If it fails, reconsider your classification.