name: draft-release-notes
Each patch release has a GitHub milestone named for it (e.g. v4.7.5), and every issue closed as completed must carry one (.github/workflows/enforce-milestone.yml reopens any that don't). The milestone is therefore the starting point for the release notes. But it only holds the issues someone assigned to it, so this skill also audits every commit since the previous release for changes the milestone misses. It then turns the issues into a section of docs/release-notes/version-X.Y.md.
The notes are published text. Present the draft to the user for review and editing; never commit it unreviewed.
gh issue list --milestone vX.Y.Z --state all --limit 200 \
--json number,title,state,stateReason,labels,url
NOT_PLANNED or DUPLICATE; only COMPLETED issues are candidates.A change whose issue was never assigned to the milestone, or was assigned to the wrong one, is invisible to step 1, even though it shipped. Check every commit since the previous release:
git fetch origin main --tags
python3 .claude/skills/draft-release-notes/audit_milestone.py vX.Y.Z
The script follows each first-parent commit on main since the previous release tag to its PR, and to the issues that the PR closes or the commit subject references (Fixes #N). It skips release commits, translation updates, and updates to the list of vouched contributors (.github/VOUCHED.td), and prints two tables:
type: label, and the areas of the codebase changed (app, dependencies, install, schema, docs, tests, tooling). Reverts are always listed, even when their issue is in the milestone. A commit marked "not on GitHub" hasn't been pushed, so its PR and issues can't be looked up.To audit a past release, pass --base vPREV --head vX.Y.Z.
Review every row; the script only finds the gaps, and deciding what's noteworthy is up to you:
gh issue edit N --milestone vX.Y.Z). Once the user confirms, it goes through the remaining steps like any other issue.gh pr view P --json title,body,files, git show --stat <sha>). Changes confined to tooling, tests, or docs are rarely noteworthy. For any other change, decide whether users would notice it: a bug fix, a change in behavior or in the API, a new feature, a performance change, or a dependency upgrade. If they would, search for an issue the PR should have referenced (gh search issues --repo netbox-community/netbox "<keywords>") and propose assigning it to the milestone. If no issue exists, tell the user; every entry links to an issue, and opening one is up to them.git log for related changes), or closed without a code change (e.g. an investigation, such as #23362 in v4.7.4). Or it may have been fixed on feature instead (git log origin/feature --oneline --grep "#N"), in which case it belongs in the next minor release's milestone, and not in these notes.Report a verdict for every row: noteworthy and how to handle it, or not noteworthy and why. Make no milestone changes without the user's confirmation.
Assign each issue by its type: label, then sort each section by ascending issue number:
| Label | Section |
|---|---|
type: feature |
### Enhancements |
type: performance |
### Performance Improvements |
type: bug |
### Bug Fixes |
type: housekeeping, type: documentation, type: translation |
Omitted |
type: deprecation, type: removal, or no type: label |
Flag for the user |
Omit any section that ends up empty.
For each included issue, read the issue body and the PR(s) that closed it:
gh issue view N --json title,body,closedByPullRequestsReferences
gh pr view P --json title,body,files
Use parallel subagents to read the issues when there are more than a dozen or so, asking each one for a proposed entry plus a one-line note on what changed.
Each entry is one line:
* [#N](https://github.com/netbox-community/netbox/issues/N) - <description>
Write the description for the end user, not from the issue title or the code:
Examples from v4.7.4 (issue title → entry):
| Issue title | Entry |
|---|---|
| Add device from location page has wrong argument | Pre-populate the location when adding a non-racked device from a location's view |
| Every non-bulk change enqueues its own Search cache update job instead of coalescing — RQ queue flood | Coalesce search cache updates rather than enqueuing a background job for each change |
| Bookmark, journal entry and related API endpoints fail for objects without a REST serializer | Fix exception when listing bookmarks, journal entries, and other objects assigned to objects which have no REST API serializer |
| Please add several more basic widely used trunk cable profiles ... | Add 8C1P, 12C1P, and 24C1P trunk cable profiles |
Recommend omitting, but let the user decide:
strawberry-graphql-django).Present every issue in a table grouped as included, omitted, and needs your decision, with a short reason for each omission.
Place a warning above the first section for anything a user or operator must act on: an API or GraphQL change they need to adapt to, a dependency upgrade requiring action (the prepare-release skill passes these in), or a new minimum version. State what to do, concretely:
!!! warning "GraphQL Circuit Status Values"
The GraphQL enum for circuit and virtual circuit status ... must be updated to use the prefixed form (e.g. `{exact: STATUS_ACTIVE}`). See [#23469](https://github.com/netbox-community/netbox/issues/23469) for details.
Use absolute URLs for links; relative links to other docs pages break in the PR and release bodies.
The section goes at the top of docs/release-notes/version-X.Y.md, directly below the # NetBox vX.Y heading, and is separated from the previous release by ---:
# NetBox vX.Y
## vX.Y.Z (YYYY-MM-DD)
!!! warning "..."
...
### Enhancements
* ...
### Bug Fixes
* ...
---
## vX.Y.(Z-1) (...)
The date is the planned release date (the milestone's due date, and published in netbox/release.yaml).
Once the section is in the docs file, render the GitHub bodies from it rather than by hand:
python3 .claude/skills/draft-release-notes/render_release_notes.py X.Y.Z --format pr # release PR body
python3 .claude/skills/draft-release-notes/render_release_notes.py X.Y.Z --format release # GitHub release body
The script drops the ## vX.Y.Z heading and converts admonitions to GitHub alerts (> [!WARNING], dropping the title). The release format also promotes ### headings to ##, as the release checklist specifies.
fix(core): ...). But the milestone workflow only catches issues closed with no milestone, not issues given the wrong one, or never linked to the fix at all. Only the commit log shows everything that shipped.gh calls by hand. Whether a change is noteworthy depends on reading it.docs/development/release-checklist.md: Update Version and Changelogdocs/release-notes/: Past release notes, for style