Repository navigation
Fix automatic release changelog promotion - #23
Conversation
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_088218e8-05a6-4652-a53b-6eb9816f5009) |
📝 WalkthroughWalkthroughThe auto-release script detects plain and bracketed ChangesUnreleased changelog promotion
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant rewrite_release_files
participant promote_unreleased
participant Changelog
rewrite_release_files->>promote_unreleased: pass changelog and release data
promote_unreleased->>Changelog: replace Unreleased heading or empty section
promote_unreleased-->>rewrite_release_files: return promoted changelog
rewrite_release_files->>Changelog: write updated content
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_95db216f-2cd9-48aa-845a-7f0ff9f11144) |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_0ea748dd-dcdd-4c9a-9a3b-a0aae427223c) |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/auto_release.py`:
- Around line 135-143: Update the curated_stems comprehension to recognize
Markdown unordered-list prefixes for -, *, and + at any indentation level, strip
the matched prefix and surrounding whitespace before calling
release_subject_stem, and preserve ignoring non-list lines. Add regression
coverage proving * and + curated entries suppress matching generated subjects.
- Around line 125-128: Update the changelog section lookup around
UNRELEASED_HEADING and find_next_changelog_section to scan from the beginning
while tracking fenced-code state, selecting only an Unreleased heading outside
fences. Add a regression case covering a changelog with only a fenced Unreleased
heading before an existing release heading, ensuring the fallback insertion path
is used.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 611b61cb-fe6a-4a0a-bd7a-13ae93a9c0cf
📒 Files selected for processing (2)
scripts/auto_release.pyscripts/test_auto_release.py
| heading = UNRELEASED_HEADING.search(changelog) | ||
| if heading is None: | ||
| return None | ||
| section_end = find_next_changelog_section(changelog, heading.end()) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Ignore fenced Unreleased headings when locating the section.
UNRELEASED_HEADING.search(changelog) can select ## Unreleased inside a fenced code block. find_next_changelog_section starts after that match, so it cannot recover the existing fence state. The release can promote code-example text instead of using the fallback insertion path.
Scan from the start of changelog while tracking fences before selecting an Unreleased heading. Add a regression case with only a fenced ## Unreleased heading before an existing release heading.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/auto_release.py` around lines 125 - 128, Update the changelog section
lookup around UNRELEASED_HEADING and find_next_changelog_section to scan from
the beginning while tracking fenced-code state, selecting only an Unreleased
heading outside fences. Add a regression case covering a changelog with only a
fenced Unreleased heading before an existing release heading, ensuring the
fallback insertion path is used.
| curated_stems = { | ||
| release_subject_stem(line[2:].strip()) | ||
| for line in body.splitlines() | ||
| if line.startswith("- ") | ||
| } | ||
| unmatched = [ | ||
| subject | ||
| for subject in subjects | ||
| if release_subject_stem(subject) not in curated_stems |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Recognize all supported Markdown bullet markers.
Line 138 only recognizes top-level - entries. A curated * fix: one, + fix: one, or indented list entry does not suppress the matching generated subject. The release then duplicates a curated change.
Match Markdown list prefixes before passing the entry text to release_subject_stem. Add regression coverage for at least * and + entries.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/auto_release.py` around lines 135 - 143, Update the curated_stems
comprehension to recognize Markdown unordered-list prefixes for -, *, and + at
any indentation level, strip the matched prefix and surrounding whitespace
before calling release_subject_stem, and preserve ignoring non-list lines. Add
regression coverage proving * and + curated entries suppress matching generated
subjects.
| heading = UNRELEASED_HEADING.search(changelog) | ||
| if heading is None: | ||
| return None | ||
| section_end = find_next_changelog_section(changelog, heading.end()) |
There was a problem hiding this comment.
🟡 Changelog can be rewritten in the wrong place when an example heading appears in a code block
The changelog's "Unreleased" heading is located with a plain text search (UNRELEASED_HEADING.search at scripts/auto_release.py:125) that ignores fenced code blocks and already-published sections, so a sample heading inside an example block gets overwritten with the new release title and the real release notes end up in the wrong place.
Impact: An automated release can silently mangle the published changelog, replacing example text and leaving the new release entries buried at the bottom of the file.
Fence-aware section-end scan is not mirrored by the heading search
find_next_changelog_section (scripts/auto_release.py:89-116) carefully skips ## headings inside Markdown fences, but the search for the Unreleased heading itself (scripts/auto_release.py:125) uses a plain multiline regex over the entire document. Any ## Unreleased line inside a fenced example (e.g. a past release entry that documents the changelog format), or any stray ## Unreleased located below existing release sections, is matched.
Reproduced with the current code:
## [0.3.0] - 2026-01-02
- feat: document the changelog format, e.g.
```markdown
## Unreleased
- note
becomes
## [1.0.0] - d
- note...
-
Old
-
feat: a
so the example is destroyed, the new heading is inside a code fence, and the generated subject is appended after the oldest release.
A fix would be to reuse the fence-tracking scan when locating the heading (e.g. walk lines from the start of the document tracking fences and stop at the first non-fenced `## [Unreleased]`/`## Unreleased`), and/or to only accept a heading that occurs before the first `## [` release section.
</details>
<details>
<summary>Prompt for agents</summary>
In scripts/auto_release.py, promote_unreleased locates the Unreleased heading with a plain regex search over the whole changelog, while find_next_changelog_section deliberately tracks Markdown code fences when locating the end of the section. This asymmetry means an ## Unreleased line that appears inside a fenced code example (for instance a past release entry documenting the changelog format) is treated as the real Unreleased heading: it gets replaced by the new release heading inside the fence and the generated subjects are appended far below, corrupting the file. The same issue applies to an ## Unreleased heading that appears after existing ## [x.y.z] release sections, which would place the new release out of order. Consider reusing the fence-aware line walk to find the heading (only accepting a heading outside any fence), and optionally requiring that it appear before the first published release section.
</details>
<!-- devin-review-badge-begin -->
<a href="https://app.devin.ai/review/jnyross/codex-graph/pull/23" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1">
<img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review">
</picture>
</a>
<!-- devin-review-badge-end -->
---
*Was this helpful? React with 👍 or 👎 to provide feedback.*
Summary
## Unreleasedand## [Unreleased]headings.Validation
Test notes
python3 scripts/test_auto_release.py -v— 11 tests passed: the existing 8 plus 3 exact review regressions.fix: one (#9) — curated details, fenced## literal heading, and malformed## [Unreleased.0.6.0, preserved the fenced curated body, suppressed the represented fix, and appended the unrelated feature once.Note
Low Risk
Changes are confined to the release script and its unit tests; behavior only affects how
CHANGELOG.mdis written during automated releases, not runtime product code.Overview
Release changelog handling no longer always prepends a fresh version block from commit subjects. When
CHANGELOG.mdhas a valid## Unreleasedor## [Unreleased]section,rewrite_release_filespromotes that section in place to## [version] - date, removes the Unreleased heading, and leaves curated Markdown (subsections, em-dash details) unchanged.Deduping compares normalized “stems” (strip
— …detail suffixes and trailing(#PR)), so a generated subject likefix: one (#9)does not duplicate a curated- fix: one — …line. Unmatched generated subjects are appended once; an empty Unreleased body still gets the full generated bullet list. Section boundaries ignore##lines inside fenced code blocks; malformed## [Unreleasedheadings are not promoted and the old prepend path remains.Tests add a shared fixture helper and regression cases for promotion, append-only extras, PR-suffix matching, fences, bracketed headings, and empty Unreleased.
Reviewed by Cursor Bugbot for commit fa1cd3e. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit