Skip to content

ci(release): stop CHANGELOG.md gaining a blank line at every release - #690

Merged
aliasunder merged 12 commits into
mainfrom
worktree-changelog-blank-lines
Oct 21, 2026
Merged

aliasunder merged 12 commits into
mainfrom
worktree-changelog-blank-lines

Conversation

@aliasunder

@aliasunder aliasunder commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

Summary

update-changelog.sh inserted 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.

  • The script now joins the title, the new entry and the earlier entries with exactly one blank line between them and a single final newline, whatever each part ends with.
  • A release that replaces an [Unreleased] section goes through the same insert. Before, that path passed the notes to re.sub as its replacement, so a backslash in the notes was read as an escape, and it left no blank line before the next entry.
  • A missing or empty CHANGELOG.md, or one with nothing above its first entry, gets the # Changelog title; empty notes still get one blank line on each side.
  • Notes passed as /dev/stdin still arrive: the Python program now comes in on another file descriptor, so it no longer uses up the caller's stdin.
  • The script reads and writes UTF-8 explicitly, so the em dash in the entry heading no longer depends on the runner's locale.
  • CHANGELOG.md's extra blank lines are removed: the 121 under the title, and a second blank line at 21 boundaries between entries from 0.13.0 down to 0.1.1 (left by an earlier version of the script). No entry text changes.

Test plan

  • src/__tests__/update-changelog.test.ts: 21 tests run the real script in a temporary directory (stub date) 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.
  • Ran the script twice on a copy of the real CHANGELOG.md: one blank line under the title each time, and every entry from 0.54.16 down byte-identical.
  • npm test, ShellCheck, markdownlint and Prettier pass.

🤖 Generated with Claude Code

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>
Comment thread .github/scripts/update-changelog.sh Outdated
@umm-actually

umm-actually Bot commented Oct 21, 2026

Copy link
Copy Markdown

Stop processing release notes as a re.sub replacement template
Low severity · correctness · high confidence

.github/scripts/update-changelog.sh:26 — beyond the diff's line ranges, in code the changes touch or depend on.

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 fix
Pass 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

@umm-actually

umm-actually Bot commented Oct 21, 2026

Copy link
Copy Markdown

Keep one blank line after the entry inserted via the [Unreleased] path
Low severity · subtle bugs · high confidence

.github/scripts/update-changelog.sh:27 — beyond the diff's line ranges, in code the changes touch or depend on.

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 fix
Use 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

umm-actually Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

umm-actually re-reviewed at 3e82002

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>
@aliasunder

Copy link
Copy Markdown
Owner Author

Both [Unreleased] findings fixed in f80b14e:

  • Stop processing release notes as a re.sub replacement template (issue comment 6321006090): valid. The [Unreleased] section is now removed with an empty replacement, and the entry is inserted by the same code every release uses, so the notes never reach re.sub.
  • Keep one blank line after the entry inserted via the [Unreleased] path (issue comment 6321006390): valid. That shared insert keeps exactly one blank line on each side of the entry.

Checked with notes containing \d, \n and C:\temp, against three changelogs: [Unreleased] above a version entry, [Unreleased] as the last section, and no [Unreleased]. Each backslash sequence is written as-is, each entry has one blank line on each side, and the file ends with a single newline.

Sweep re.sub( over .github/ and scripts/: 1 hit, this call.


🔍 ship-check · pr-monitor · claude-opus-5-5

@umm-actually

umm-actually Bot commented Oct 21, 2026

Copy link
Copy Markdown

Collapse the double blank lines left between the older changelog entries
Low severity · subtle bugs · high confidence

CHANGELOG.md:2500 — beyond the diff's line ranges, in code the changes touch or depend on.

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 fix
Collapse 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>
@aliasunder

Copy link
Copy Markdown
Owner Author

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

Comment thread .github/scripts/update-changelog.sh Outdated
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>
Comment thread .github/scripts/update-changelog.sh Outdated
aliasunder and others added 2 commits October 21, 2026 13:37
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>
Comment thread .github/scripts/update-changelog.sh Outdated
aliasunder and others added 5 commits October 21, 2026 14:00
…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>
Comment thread .github/scripts/update-changelog.sh
Comment thread src/__tests__/update-changelog.test.ts Outdated
Ship-Check: pr-monitor · claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@aliasunder
aliasunder merged commit 6e15b71 into main Oct 21, 2026
18 checks passed
@aliasunder
aliasunder deleted the worktree-changelog-blank-lines branch October 21, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant