๐Ÿ“‹ Postmortem: release-please version drift

2026-05-22 โ€” Wrong major-version bump proposed on main due to manual edits to release-please's governed files inside a squashed PR. Resolved + 3-layer prevention shipped.

โฑ Timeline

09:30 UTC SETUP PR #1919 merged: feat!: hard-delete monitors.json. The ! queues a future MAJOR bump.
10:52 UTC OK release-please PR #1922 merged โ†’ v8.0.0 tagged + released cleanly.
13:28 UTC ROOT CAUSE PR #1920 squashed with inner commit chore: bump to v8.1.0 that hand-edits 9 governed files. release-please's state machine desyncs.
13:30 UTC SYMPTOM release-please opens PR #1946 proposing 9.0.0. Compare URL says compare/v8.1.0...v9.0.0 โ€” but neither tag exists.
~15:30 UTC DETECTED "how the fuck 1946 is 9.0.0 thats a bug we by no way move to 9.0.0"
15:38 UTC FIX PR #1947 merged: reverts 9 files to 8.0.0 + 3 prevention layers (CI guard, CONTRIBUTING note, diagnosis playground).
15:39 UTC RESOLVED release-please re-runs, sees clean 8.0.0 anchor, updates PR #1946 in place: title โ†’ chore(main): release 8.1.0.
15:52 UTC SHIPPED PR #1946 merged โ†’ v8.1.0 released correctly.
17:30 UTC FOLLOWUP PR #1948 fixes a latent skip-list regex (issue/ + bug/ prefixes were missing from version-check.yml + pre-push hook).

๐Ÿ” Root cause

release-please needs to find a git tag matching the version in .release-please-manifest.json to know where to start scanning commits. When the manifest says 8.1.0 but no v8.1.0 tag exists, it falls back to the bootstrap-sha baseline and re-scans the full history โ€” re-applying every feat!: and BREAKING CHANGE: it finds, even ones already shipped. Result: wildly wrong major bumps.

๐Ÿ›ก Prevention (3 layers, PR #1947)

LayerFileHow it protects
1 .github/workflows/release-please-guard.yml Reads governed file list from .release-please-config.json at runtime, fails any non-bot PR touching them. Bots + release-please-override label bypass.
2 CONTRIBUTING.md "Do not hand-edit release-please's governed files" subsection under Versioning, references this incident.
3 docs/fix--release-please-version-drift/release-please-drift.html Diagnosis playground with before/after table + step-by-step root-cause walkthrough.

๐ŸŽฏ Lessons

LessonAction
Squashed PR bodies hide intent โ€” an inner chore: bump to vX.Y.Z commit can land without standalone review. Guard blocks based on file diffs, not commit messages.
.release-please-config.json extra-files is the SOURCE for "what release-please owns." Guard reads this file at runtime so the list stays in sync automatically.
release-please's failure mode is silent โ€” wrong version, no error. Treat any major-bump draft on a non-breaking commit cluster as a red flag.
The override label exists for emergencies only โ€” drift fixes, schema changes. Use sparingly; document the reason in PR body.