Skip to content

ci(release-docs-check): assert [Unreleased] drained on release PR (#837) - #838

Merged
github-actions[bot] merged 1 commit into
mainfrom
ci/issue-837-release-docs-unreleased-drain
May 15, 2026
Merged

ci(release-docs-check): assert [Unreleased] drained on release PR (#837)#838
github-actions[bot] merged 1 commit into
mainfrom
ci/issue-837-release-docs-unreleased-drain

Conversation

@robotrocketscience

Copy link
Copy Markdown
Owner

Closes #837.

Bug

release-docs-check in .github/workflows/staging-gate.yml detects release PRs and asserts the ## [X.Y.Z] header and compare-link footnote exist in CHANGELOG/v{major}.md, but does not assert that [Unreleased] was actually drained into the new dated section. The project policy (top-level CHANGELOG.md) explicitly says CI checks both; only the dated-section half was implemented.

The gap surfaced on PR #835 (release: v3.1.1): the PR reached attn:review while five [Unreleased] entries — including #814's breaking-change Removed block — sat undrained, which would have shipped under a SemVer-patch tag.

Fix

Extend the release-docs-check job with one awk pass over the changelog file:

/^## \[Unreleased\]/ { in_unreleased=1; next }
/^## \[/ && in_unreleased { exit }
in_unreleased && NF > 0 && !/^###/ { print }
  • Sets in_unreleased=1 on the ## [Unreleased] header
  • Exits at the next ## [ header (typically ## [X.Y.Z])
  • Inside the window: prints any line that is non-whitespace AND not a ### ... subsection header

Output non-empty → bullet / paragraph content remains in [Unreleased] → fail with an actionable error message that points at the policy and previews the offending lines (head -20).

Empty ### Added / ### Fixed / ### Removed scaffolding is preserved (common pattern for clarity); only entries-under-headers count as un-drained.

Verification

Smoke-tested the awk logic locally against three cases:

case input awk output verdict
current CHANGELOG/v3.md non-drained [Unreleased] first 3 bullets surface would fail a release PR (correctly)
synthetic drained ## [Unreleased]\n\n### Added\n\n### Fixed\n\n## [9.9.9]... empty passes (correctly)
synthetic non-drained as above + - leftover entry under ### Added - leftover entry fails (correctly)

The PR itself does not bump pyproject.toml, so the existing head_ver == base_ver short-circuit at the top of the job means this PR's own release-docs-check run will exit 0 (correctly — this is a CI-config PR, not a release PR).

Out of scope

  • SemVer-level guard ("patch bump shipping Removed entries with breaking-change language"). Detecting that mechanically requires text classification; the Unreleased-drain check catches the symptom at the release boundary instead.
  • Lifting the awk to a standalone scripts/check-changelog-drained.sh with its own unit tests. Worth doing if this check grows more clauses; currently 4 lines of awk is small enough to keep inline.
  • The independent release-drafter / publish.yml draft-promotion gap (separate work).

Tier

rook — single-workflow patch, 21 lines added, no production code touched.

#837)

The existing release-docs-check job in staging-gate.yml asserts that a
release PR's CHANGELOG/v{major}.md carries the `## [X.Y.Z]` header plus
a compare-link footnote, but it does not assert that `[Unreleased]` was
actually drained. Top-level CHANGELOG.md is explicit about the policy
("On release, move [Unreleased] content into a dated ## [X.Y.Z] section
and add the compare-link footnote at the bottom of the same file. CI
checks both.") — CI was only checking the dated-section side.

The gap let a release PR (#835) reach `attn:review` while five entries
including a Removed-with-breaking-changes block stayed in
`[Unreleased]`, which would have shipped under a patch tag if labeled
ready-to-merge. The check below catches that mechanically.

Awk walks the file between `## [Unreleased]` and the next `## [`
header, printing any non-whitespace line that is not a `### ...`
subsection header. Output non-empty = bullet/paragraph content remains
in [Unreleased] = fail.

Locally smoke-tested against three cases:
- current CHANGELOG/v3.md (non-drained [Unreleased]) → produces output
- synthetic drained [Unreleased] (subsection headers only) → empty
- synthetic mixed (one leftover bullet) → outputs that bullet
@robotrocketscience robotrocketscience added the author-prince Authored by parallel session: prince label May 15, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 2 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: afbe63d4-86ea-47f0-b5a0-ccfdeb094a88

📥 Commits

Reviewing files that changed from the base of the PR and between d3ea7ef and 75d27b3.

📒 Files selected for processing (1)
  • .github/workflows/staging-gate.yml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/issue-837-release-docs-unreleased-drain

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label May 15, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:oppenheimer:2026-05-15T02:58:17Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:bagheera:2026-05-15T02:58:28Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:bagheera:2026-05-15T02:58:32Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Approve — clean CI guard, semantically correct

21-line patch to release-docs-check, single signed commit (75d27b38 G), FF on current main, mergeable=CLEAN, discretion grep on full diff = 0 hits. Gated by the existing head_ver != base_ver short-circuit so non-release PRs (this one included) are unaffected.

Awk smoke-tested locally against four cases — all correct:

input awk output verdict
current CHANGELOG/v3.md bullets from [Unreleased] § Fixed / Added would correctly fail a release PR today
[Unreleased] with empty ### Added / ### Fixed scaffolding only empty passes
[Unreleased] with - leftover entry under ### Added - leftover entry fails (correct)
changelog with no [Unreleased] header at all empty passes (correct)

Edge cases I worked through and are handled:

  • [Unreleased] is the last section in the file (no following ## [): awk's exit clause never fires but printing reaches EOF correctly. No false positive.
  • Bullet lines containing ### foo mid-line (e.g., - the ### foo case): line starts with -, not ###, so !/^###/ correctly keeps it as content. No false negative.
  • The next after setting in_unreleased=1 correctly prevents the ## [Unreleased] header itself from being printed.

One forward-looking note worth flagging on the next iteration (not a blocker): if anyone ever adds a ## Notes or other top-level non-version ## section inside [Unreleased], awk would treat its body as undrained content. Not realistic in keepachangelog format; calling out so reviewers know it's known.

Adding ready-to-merge. Releasing claim.

@robotrocketscience robotrocketscience added ready-to-merge Trigger merge-train: FF main to this PR's head and removed attn:review Needs review (PR open, awaiting reviewer) labels May 15, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:oppenheimer:2026-05-15T03:00:23Z]

@github-actions
github-actions Bot merged commit 75d27b3 into main May 15, 2026
34 of 36 checks passed
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label May 15, 2026
@github-actions

Copy link
Copy Markdown

merge-train: merged 75d27b3main via FF push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

author-prince Authored by parallel session: prince

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(release-docs-check): assert [Unreleased] drained into dated section on release PR

1 participant