Repository navigation
ci(release): stop CHANGELOG.md gaining a blank line at every release - #690
Conversation
update-changelog.sh inserted each entry with a leading newline below a header that already ended in a blank line, so every release added one more blank line under the title. It now keeps exactly one blank line on each side of the new entry, and the existing blank lines are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Stop processing release notes as a re.sub replacement template
Pre-existing: the [Unreleased] branch passes the assembled entry as re.sub's replacement string, so Python processes backslash sequences in the release notes as replacement-template escapes. A note containing an escape like backslash-d crashes the script with re.error, while backslash-n or backslash-t are silently converted into real newline or tab characters, corrupting the written entry. Reachability depends on an Unreleased section existing, which the current CHANGELOG.md does not carry. Failure scenario: A release run where an Unreleased section exists and the notes file contains the two-character sequence backslash-d (or any unknown letter escape) aborts the changelog update with a re.error bad-escape failure; notes containing backslash-n or backslash-t get those sequences turned into real line breaks or tabs inside the committed entry. Suggested fixPass a function as the replacement so the text is used verbatim: re.sub(pattern, lambda _match: entry, text, count=1, flags=re.DOTALL).umm-actually · z-ai/glm-5.3-flash |
|
Keep one blank line after the entry inserted via the [Unreleased] path
Pre-existing: the [Unreleased] replacement consumes the matched section's trailing newline, so when a version entry follows, the next heading ends up immediately after the new entry's last line with no blank line between them. This contradicts the exactly-one-blank-line-on-each-side invariant the newly added insertion comment states, and every other entry boundary in the file keeps one. Failure scenario: CHANGELOG.md contains an Unreleased section above an existing version entry; running the script writes the new entry's last line directly against the following heading with only a single newline — the blank line separating the two entries is gone, unlike every other boundary in the file. Suggested fixUse a function replacement that restores the separator (lambda _match: entry + "\n") together with the template-processing fix, and confirm the Unreleased-is-last-section case still ends with a single final newline.umm-actually · z-ai/glm-5.3-flash |
|
umm-actually re-reviewed at No new findings (9 tracked finding(s) across all runs). umm-actually · z-ai/glm-5.3-flash |
… as any other The [Unreleased] branch passed the release notes to re.sub as its replacement, so a backslash in the notes was read as an escape (an unknown one aborted the update), and it left no blank line before the next entry. The section is now removed and the entry goes through the same insert as every release, with one blank line on each side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Both
Checked with notes containing Sweep 🔍 ship-check · pr-monitor · claude-opus-5-5 |
|
Collapse the double blank lines left between the older changelog entries
The blank-line cleanup removed only the pileup under '# Changelog'; about 20 boundaries between the older entries (0.13.0 down to 0.3.0, plus 0.1.2→0.1.1) still separate with two blank lines. The fixed script copies that region through verbatim on every future release, so the file never reaches the one-blank-line shape this PR establishes — and collapsing them would still meet the PR's own 'no entry text changes' scope. Failure scenario: Open the committed CHANGELOG.md: the boundary at '- Update CHANGELOG.md for v0.12.1' → '## [0.12.1] — 2026-05-18' (and the ~19 others down to '## [0.3.0]' → '## [0.2.0]' and '## [0.1.2]' → '## [0.1.1]') shows two consecutive blank lines while every 0.54.x-era boundary shows one; the next release's run of the fixed script re-emits the older region byte-for-byte, so the residue persists indefinitely. Suggested fixCollapse the consecutive blank lines between entries in the 0.13.0-and-older region to one, the same way the header region was cleaned — a blank-line-only pass, keeping every entry's text byte-identical as the header cleanup did.umm-actually · z-ai/glm-5.3-flash |
Entries from 0.13.0 down to 0.1.1 were separated by two blank lines, left by an earlier version of the changelog script. Each run is now one blank line; no entry text changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Collapse the double blank lines left between the older changelog entries (issue comment 6321466579): valid, fixed in 068d350. All 21 runs of two blank lines (0.13.0 down to 0.1.1) are now one blank line; the diff deletes 21 blank lines and changes no other line. Sweep: runs of 3+ newlines in CHANGELOG.md, the repo's only changelog: 21 before, 0 after. 🔍 ship-check · pr-monitor · claude-opus-5-5 |
A CHANGELOG.md that existed but held only whitespace got a release entry with no '# Changelog' title. A missing file is now read as empty, and an empty header falls back to the title, so both get the same first lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With an empty notes file the entry ended in the heading's own blank line, so the separators added around it made extra blank lines. The assembled entry is now stripped of trailing whitespace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing [Unreleased] An [Unreleased] section below the newest entry left its separating blank line at the end of the file once removed; the earlier entries are now stripped and the file ends with a single newline. Ship-Check: pr-review · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne join Read the notes file in Python instead of passing its contents as an argument, name the two regexes, split the file at the first entry heading with re.search, and build the output from one join of the stripped parts. Comments now match what the code does. Output is byte-identical. Ship-Check: code-quality · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entry heading holds an em dash, so writing it under a non-UTF-8 locale failed. The notes and CHANGELOG.md are now read and written with an explicit UTF-8 encoding. Ship-Check: triage · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lines Runs the real script under bash in a temporary directory per test, with a stub date on PATH, and asserts the whole CHANGELOG.md it writes: the title for missing, empty, whitespace-only and title-less files; one blank line around the entry across repeated releases; [Unreleased] replaced above an entry, as the last section, or opening the file; backslashes in the notes written as-is; empty notes; UTF-8 under an ASCII locale; and a missing notes file leaving CHANGELOG.md untouched. Ship-Check: test-audit · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Python program came in on stdin through a heredoc, so a notes file of /dev/stdin read the already-consumed heredoc and the entry was written with no notes, without an error. The program now comes in on fd 3, leaving stdin to the caller. Two comments are reworded to match the code: the [Unreleased] comment no longer says the entry goes where the section was (it goes above the newest entry), and the header comment names the # Changelog title it supplies. Ship-Check: bug-check · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
spawnSync's input reaches the child through a socket on Linux, and a socket cannot be opened as /dev/stdin, so the test failed in CI while passing on macOS. Standard input now comes from the notes file's descriptor, as a shell redirect gives it. Ship-Check: pr-monitor · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ship-Check: pr-monitor · claude-opus-5-5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
update-changelog.shinserted each release entry with a leading newline, below a header that already ended in a blank line. Every release added one more blank line under# Changelog, so the file now starts with 122 of them.[Unreleased]section goes through the same insert. Before, that path passed the notes tore.subas its replacement, so a backslash in the notes was read as an escape, and it left no blank line before the next entry.# Changelogtitle; empty notes still get one blank line on each side./dev/stdinstill arrive: the Python program now comes in on another file descriptor, so it no longer uses up the caller's stdin.Test plan
src/__tests__/update-changelog.test.ts: 21 tests run the real script in a temporary directory (stubdate) and assert the whole CHANGELOG.md — placement and blank lines across two releases, the title cases,[Unreleased]above, last and first with backslashes in the notes, empty notes, notes piped to/dev/stdin, UTF-8 under an ASCII locale, and a missing notes file. Each of 12 mutations fails at least one test; main's version of the script fails 18.npm test, ShellCheck, markdownlint and Prettier pass.🤖 Generated with Claude Code