Render GitHub Release notes¶
notes turns one reviewed changelog section into a GitHub Release body. Run it from the tagged checkout so the
description matches the source that was published. It reads the changelog and prints Markdown to standard output; it
does not edit the changelog or create the GitHub Release.
A complete Markdown example¶
Suppose docs/changelog.md contains these two releases:
# Changelog
<a name="release-1-2-3"></a>
## 1.2.3 (2026-10-18)
<a name="release-1-2-3-fixed"></a>
### Fixed
- Avoid stale cache reads ([#42](https://github.com/owner/project/pull/42)) by [@alice](https://github.com/alice).
<a name="release-1-2-2"></a>
## 1.2.2 (2026-09-20)
### Fixed
- Reject invalid cache keys by [@alice](https://github.com/alice).
Render only 1.2.3:
uvx --from release-creator release-creator notes \
--changelog docs/changelog.md \
--tag 1.2.3 \
--repository owner/project \
--previous 1.2.2 \
--artifact-url https://pypi.org/project/project/1.2.3/ \
> release-notes.md
--tag selects one section. --repository builds GitHub links. --previous selects the start of the comparison;
the tags command can find it. --artifact-url adds the published package link.
release-notes.md contains the complete GitHub description:
<a name="release-1-2-3-fixed"></a>
### Bug fixes
- Avoid stale cache reads ([#42](https://github.com/owner/project/pull/42)) by @alice.
[PyPI release](https://pypi.org/project/project/1.2.3/)
**Full Changelog**: https://github.com/owner/project/compare/1.2.2...1.2.3
Review that file before passing it to gh release create --notes-file release-notes.md. The by credit stays in
the changelog; the GitHub body uses an @alice mention.
Rules and failure cases¶
The tagged changelog decides which changes appear, their order, their categories, and their authors. notes does not
infer changes or people from commits. Add only user-visible changes to the changelog. Credit verified human authors
there; do not credit bots or reviewers for work they did not author.
notes requires exactly one section for --tag and rejects unknown categories. It renames Fixed to Bug
fixes and Added to Features while keeping each release bullet on one line. For Markdown, leave empty headings
out of the tagged changelog; the supplied Towncrier template does this. The reStructuredText reader omits empty
categories. If the source section already has a Full Changelog line, notes replaces it with one link built from
--previous and --tag. The source’s category anchors remain stable. See the format reference for every category name.
Omit --artifact-url if there is no external artifact. Use --artifact-label to name another ecosystem’s package
link. If --previous is absent, the final link points to the commits at this tag instead of a comparison. Artifact
links must use HTTPS.
First-time contributors¶
GitHub’s generated notes may identify first-time contributors. Save its body and pass it as a second input:
gh api -X POST repos/owner/project/releases/generate-notes \
-f tag_name=1.2.3 --jq .body > generated-notes.md
uvx --from release-creator release-creator notes \
--changelog docs/changelog.md --tag 1.2.3 --repository owner/project \
--generated-notes generated-notes.md > release-notes.md
Human New Contributors lines remain even when their PR did not need a changelog entry. Generated change lists and
the <!-- Release notes generated ... --> banner are discarded. This input never decides which changes or changelog
authors belong in the release. If GitHub omits a referenced PR from its generated notes, the tagged changelog entry must
already credit its author.
reStructuredText changelogs¶
Use a .rst changelog path to select the reStructuredText reader:
uvx --from release-creator release-creator notes \
--changelog docs/changelog.rst --tag 1.2.3 \
--repository owner/project > release-notes.md
:issue: links target issues; :pr: and :pull: target pull requests. :user: becomes an @handle
mention.
Nested lists stay nested. .. code-block:: directives become fenced Markdown blocks inside their release items.
Top-level .. warning:: directives become separate warning blocks after the preceding item.