fix(commit): verify branch base is current before writing CHANGELOG - #126
Conversation
Adds Step A.0 to Workflow A, run before composing any changelog entry: git fetch git diff origin/master -- CHANGELOG.md | grep -E '^[-+]## ' Only '+## Unreleased' is acceptable. A '-## vX.Y.Z' line means a release was cut after the branch point -- '## Unreleased' was renamed upstream, so the branch's bullets merge INTO the released section. Git resolves that cleanly with no conflict marker, so nothing warns anyone. Two silent consequences: the tag and the CHANGELOG disagree about what shipped, and with no '## Unreleased' block left the release watcher has nothing to cut, so the branch's work never gets released at all. On autoRelease repos the trigger is usually your OWN merge -- merging PR-1 cuts a release that stales the base for PR-2 minutes later, no second contributor needed. So the check belongs before every commit, not once per session. The guard already existed in operator memory; nothing ran it. The command that actually writes CHANGELOG now does. Observed 2026-08-26 on bborbe/nuke: missed once and needed a repair PR to unfold a released section, then caught on the next two commits the same afternoon. Excludes unrelated dirty bench/reports/ files via explicit pathspec.
There was a problem hiding this comment.
I've reviewed the diff and evaluated all three concerns. Here's my assessment:
Concern 1 (documentation-only guard): Not applicable to this PR. This PR is the documentation — it adds Step A.0 to commands/commit.md. The concern is a general adoption question, not a code defect in this change.
Concern 2 (git reset --hard discarding changes): Addressed. The text explicitly says # save your non-CHANGELOG edits first inline in the recovery block — the warning is present in the doc.
Concern 3 (grep silent pass on empty ## headings): Confirmed — this is a real false-negative. grep -E '^[-+]## ' returns exit code 1 (no matches) on an empty result, which in a script would be indistinguishable from "only +## Unreleased found." If a CHANGELOG has body text but no section headers, the check silently passes when it should alert the user. This is a real gap, but it's a pre-existing limitation of the check's design, not something introduced by this PR (which correctly documents the intended behavior).
The PR correctly:
- Adds the
## Unreleasedentry toCHANGELOG.mdwith a well-written explanation - Documents Step A.0 with precise recovery instructions
- Warns about
autoRelease: trueper-commit necessity
No mechanical findings. Only the grep silent-pass concern (pre-existing, not introduced) warrants noting.
{
"verdict": "approve",
"summary": "PR correctly documents Step A.0 guard in commands/commit.md and adds the corresponding CHANGELOG entry. The grep silent-pass concern (grep returns exit code 1 with zero output when no ## headings exist) is a pre-existing design limitation, not introduced by this change. The documentation is clear and includes the safety warning for git reset --hard.",
"comments": [
{
"file": "commands/commit.md",
"line": 297,
"severity": "minor",
"message": "grep -E '^[-+]## ' silently passes (zero output, exit code 1) when CHANGELOG.md has no ## headings, giving a false-positive clean result. Consider adding: grep -qE '^[-+]## ' && git diff ... | grep -E '^[-+]## ' || { echo 'WARNING: no section headers found in CHANGELOG.md diff — verify ## Unreleased exists upstream'; }"
}
],
"concerns_addressed": [
"correctness: Step A.0 guard is documentation-only — not an issue in this PR; the PR IS the documentation adding the guard",
"correctness: git reset --hard warning — addressed; inline comment '# save your non-CHANGELOG edits first' is present",
"correctness: grep silent pass on empty ## headings — confirmed pre-existing limitation, not introduced by this PR"
]
}
The bug this prevents
On
autoRelease: truerepos, merging a PR triggers a release that renames## Unreleased→## vX.Y.Z. A branch created before that cut is instantly stale, and its changelog bullets merge into the released section — cleanly, with no conflict marker.No concurrency required — your own merge stales your own base, minutes apart.
Two consequences, both silent:
## Unreleasedblock left, the watcher has nothing to cut — the second PR's work never gets released.The fix
Step A.0 in Workflow A, before any entry is composed:
Only
+## Unreleasedis acceptable. A-## vX.Y.Zline means refresh the base and re-apply.Why here and not in a memory file
The guard was already documented in operator memory. It didn't help, because nothing ran it — it depended on remembering, at exactly the moment you're focused on writing a good changelog entry. This command is the artifact that writes CHANGELOG and pushes; it is the only thing guaranteed loaded at the moment the mistake happens.
Evidence
Observed 2026-08-26 on
bborbe/nuke, three times in one afternoon by one author:v0.18.0— needed repair PR #87-## v0.19.0 / +## Unreleased)-## v0.20.1 / +## Unreleased)Dogfooded: this PR's own changelog entry was written after running Step A.0.
Scope:
CHANGELOG.md+commands/commit.mdonly — the repo had unrelated dirtybench/reports/files, excluded via explicit pathspec.