fix: stop doubling the changelog header newlines - #79
Conversation
Every line of the [changelog] header template in cliff.toml ended with a literal \n escape on top of the newline a TOML multi-line string already contributes, so each line emitted two newlines. The rendered header carried three blank lines between the markdownlint configure comment and "# Changelog", which markdownlint rejects as MD012. Locally the pre-commit markdownlint --fix hook collapsed them before anything reached a commit, so the checked-in changelog looked fine. The changelog-autoupdate workflow only lints, so every scheduled run since at least 2026-06-29 failed on MD012 without ever opening a PR. Write the blank lines as blank lines. Verified against git-cliff 2.14.1, the version CI installs: regeneration now lints clean and is idempotent, and the generated header matches the committed changelog byte for byte. Add tests/test-cliff-header.sh, which decodes the template the way TOML does and asserts the MD012 invariant. It needs no git-cliff, which CI does not install, so a rendering test would have skipped there silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #79 +/- ##
==========================================
+ Coverage 47.23% 47.93% +0.69%
==========================================
Files 30 31 +1
Lines 1808 1863 +55
==========================================
+ Hits 854 893 +39
- Misses 954 970 +16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PR Summary by QodoFix duplicate newlines in generated changelog headers
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
|
/agentic_review |
There was a problem hiding this comment.
🟡 Changes recommended
The new test’s blank-line detection and header-block parsing are slightly brittle and can miss whitespace-only blank lines or fail on harmless TOML reformatting, undermining the regression guard.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Fixes the git-cliff changelog header template so it no longer emits consecutive blank lines (which break markdownlint MD012 in the Changelog autoupdate workflow), and adds a unit test to prevent regressions.
Changes:
- Update
cliff.toml[changelog].headerto use literal blank lines instead of trailing\nescapes. - Add
tests/test-cliff-header.shto decode the TOML header template and assert MD012/trailing-blank-line invariants without requiring git-cliff. - Wire the new test into
tests/test-unit.sh.
File summaries
| File | Description |
|---|---|
cliff.toml |
Removes trailing \n escapes from the header template and documents why to avoid MD012 failures in CI. |
tests/test-cliff-header.sh |
New test that renders the header template and checks for consecutive blank lines and correct trailing spacing. |
tests/test-unit.sh |
Includes the new cliff header test in the unit test suite. |
Review details
Suppressed comments (1)
tests/test-cliff-header.sh:86
- Trailing blank-line counting has the same issue as the MD012 check: it ignores whitespace-only lines. Using a whitespace-aware blank test keeps this aligned with how most markdown tooling treats blank lines.
trailing_blanks="$(awk '{ if ($0 == "") blanks++; else blanks = 0 } END { print blanks + 0 }' "$rendered")"
- Files reviewed: 3/3 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Code review by qodo was updated up to the latest commit f5656e7 |
The script runs under `set -uo pipefail` with no `errexit`, so a failed `mktemp -d` left `work` set to the empty string and execution continued. `rendered` then resolved to `/header.md`: as a normal user the redirect fails and the suite reports a confusing render failure, and as root it writes a stray file into the filesystem root that the `rm -rf "$work"` trap cannot clean up (it expands to a no-op on an empty operand). Check the assignment and exit before any path is built from it. Reported by qodo-code-review on PR #79. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
markdownlint treats a whitespace-only line as blank, but both awk checks tested for an empty line only. A header carrying a line of spaces next to an empty one therefore passed this suite while markdownlint failed it -- the exact divergence the test exists to prevent, since it stands in for the autoupdate workflow's markdownlint run. Verified against markdownlint-cli 0.48.0 with the repo's own config: a header rendering "Some text." / three spaces / empty / "More text." is reported as MD012 Actual: 2, and is now caught here too. Reported by copilot-pull-request-reviewer on PR #79 (lines 73, 86). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment sat under a line naming the header, body and footer keys and then said "blank lines here", which reads as a file-wide ban on trailing \n escapes. The body template 30 lines below legitimately uses them: its source lines are joined with backslash continuations, so it places newlines explicitly. Say which template the rule governs, and why the body differs, so the note that exists to stop the MD012 bug returning cannot be read as forbidding what the body already does. Reported by copilot-pull-request-reviewer on PR #79. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
[Review-Convergence] Qodo determination — head f5656e7 1 listed finding: 1 fixed. 0 actionable remaining. Per-item determinations
The four items struck through in the summary are the previous round's dispositions, already reflected by Qodo itself; they are not re-triaged here. |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit 998e5f8 |
|
[Review-Convergence] Round 1: active
Round 1 dispositions (4 bot threads)
Codex has never engaged with this PR, so it is not eligible for re-request. Quorum of 2 is reachable from Copilot ( |
There was a problem hiding this comment.
🟢 Approval recommended
The changes directly address the documented CI root cause and add a focused regression test without introducing new behavioral risks.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
[Review-Convergence] Round 2: converged
Clean-signal evidence
Codex has never engaged with this PR at any point, so it was never eligible for re-request. Quorum was met from the engaged set without it. Converged in 2 rounds: 4 bot threads triaged (3 fixed across f624b03, ee335df, 998e5f8; 1 evidenced wontfix), 3 commits pushed, both reviewers clean on the resulting head. This runner does not merge — landing the PR remains your call, squash-only per repo policy. |
Fixes the
Changelog autoupdateworkflow, which has failed on every scheduled run since at least 2026-06-29 — most recently run 34793753205.Root cause
Every line of the
[changelog] headertemplate incliff.tomlended with a literal\nescape on top of the newline a TOML multi-line basic string already contributes, so each source line emitted two newlines. The rendered header came out as:markdownlint's MD012 caps consecutive blank lines at 1, which is exactly the
CHANGELOG.md:3/CHANGELOG.md:4errors in the run log.It stayed invisible because the pre-commit
markdownlint --fixhook collapses the blanks locally, so the checked-inCHANGELOG.mdhas always looked correct. TheFormat CHANGELOG.mdstep in the workflow only lints (markdownlint-cli2-actiondefaults tofix: false), so regenerating in CI reintroduced them and the job died before ever reaching the create-PR step. No autoupdate PR has ever been opened.Fix
cliff.toml— blank lines written as blank lines,\nescapes dropped, with a comment recording why so it does not regress.tests/test-cliff-header.sh(new, wired intotest-unit.sh) — decodes the header template the way TOML does and asserts the MD012 invariant. It deliberately needs no git-cliff, because CI does not install it and a rendering test would have skipped there silently. It fails on the old template at rendered lines 3 and 4, matching CI's report.Hardening from review
Bot review of the new test produced three follow-up commits:
mktemp -d. The script runs withouterrexit, so a failed temp-dir creation leftworkempty and the render target resolving to/header.md; as root that writes a stray file into the filesystem root which the cleanup trap cannot remove.cliff.tomlcomment to the header template, so it cannot be read as banning the\nescapes thebodytemplate deliberately relies on.One finding was declined with evidence: the strict
header = """anchor stays exact, becausetomllibconfirms trailing whitespace after the delimiter changes the rendered header rather than being a harmless reformat.Verification
Run against git-cliff 2.14.1, the version
taiki-e/install-actioninstalls in CI (2.14 changed--prependbehavior, so testing on an older local build would not have been conclusive):cliff.tomlupdate-unreleased.sh→ markdownlintCHANGELOG.mdbyte for byte.pre-commit run markdownlint prettieron the generated file: passed, no modifications — so the autoupdate PR this unblocks will also pass its own CI..markdownlint.ymlconfirms the rule the test mirrors: a whitespace-only line counts toward MD012.shellcheck,shfmt, andpre-commitclean on all changed files.Noted, not changed
scripts/release/stamp-changelog.shemits MD032 (blanks-around-lists) in the oldest section, from thebodytemplate rather than the header. Pre-existing and harmless —release-pr.ymlrunspre-commit run --files CHANGELOG.md, which auto-fixes it. Worth a separate cleanup if the raw output should be canonical.Format CHANGELOG.mddoes not format. With the root cause fixed it is a correct guard, and addingfix: truewould change workflow behavior, so I left it alone.🤖 Generated with Claude Code
Closes #80 (review-convergence bulletin)