Release notes from your changelog, on every tag.
Publish a release or push a tag, and this Action fills in the GitHub release from the matching CHANGELOG.md section, breaking changes first. No changelog? It writes the notes from your commits since the last tag.
Copy this workflow · the whole setup
name: Release notes
on:
release:
types: [published]
permissions:
contents: write
jobs:
notes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: kalmwerk/release-notes-action@v1
Save it in your repository and publish a release: the notes appear on it seconds later. @v1 follows every 1.x release. Other triggers and pinning.
Rather keep a copy in your own repository? Download the Action (26 KB zip)
- No API key or sign-up
- Runs inside your workflow
- MIT licensed
You write · CHANGELOG.md
## [3.0.0] - 2026-11-03
Two breaking changes: `connect()` is async, and `Client.raw()` is gone.
### Added
- `Client.batch()` sends up to 100 queries in one round trip ([#401]).
### Changed
- **Breaking:** `connect()` is now async. Replace `db = connect(url)` with `db = await connect(url)`.
- Error messages include the line and column ([#398]).
### Deprecated
- `Client.legacy_query()`: use `Client.query()`. It will be removed in 4.0.0.
### Removed
- **Breaking:** `Client.raw()`, deprecated since 2.4.0. Use `Client.query(raw=True)`.
### Fixed
- Timeouts apply to DNS lookups ([#405]).
[#398]: https://github.com/acme/db/pull/398
[#401]: https://github.com/acme/db/pull/401
[#405]: https://github.com/acme/db/pull/405The Action writes · GitHub release
Two breaking changes: connect() is async, and Client.raw() is gone.
⚠️ Breaking changes
connect()is now async. Replacedb = connect(url)withdb = await connect(url).- Removed
Client.raw(), deprecated since 2.4.0. UseClient.query(raw=True).
Added
Client.batch()sends up to 100 queries in one round trip (#401).
Changed
- Error messages include the line and column (#398).
Deprecated
Client.legacy_query(): useClient.query(). It will be removed in 4.0.0.
Fixed
- Timeouts apply to DNS lookups (#405).
Full changelog: https://github.com/acme/db/compare/v2.4.1...v3.0.0
A fictional project. The release body above is produced by the Action's own code from the section on the left.
Install
One file in .github/workflows.
Pick when it should run, save the file, and the next release gets its notes.
- 1Add the workflowSave one of the files on the right in your repository. Each runs
kalmwerk/release-notes-action@v1. - 2Let it edit releasesThe job needs
contents: write. The default workflow token is enough. - 3Release as usualPublish a release or push a tag. The notes appear on the release and in the job summary.
Pin a version
@v1 moves to each 1.x release. For an exact version, pin the release tag (v1.0.0 release notes):
uses: kalmwerk/release-notes-action@v1.0.0Or keep a copy in your repository
- Download the Action (the same code, as a 26 KB zip).
- Unzip it into
.github/actions/release-notes/and commit it. - In the workflow, replace the
uses:line with the local path, after the checkout step:
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/release-notesname: Release notes
on:
release:
types: [published]
permissions:
contents: write
jobs:
notes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: kalmwerk/release-notes-action@v1
Publish a release on GitHub (with an empty description, or GitHub's own) and the notes are filled in a few seconds later.
How it works
Your changelog first. Your commits if there isn't one.
- Tag → versionv1.2.0, 1.2.0, pkg@1.2.0 and release-1.2 all work. Tags without a version can pass one in.
- The matching CHANGELOG.md sectionKeep a Changelog 2.0.0's summary paragraph and in-place Breaking: markers included. Breaking changes go first with their migration steps, then each change once under its type.
- Or the commits since the previous tagThe previous tag is the highest earlier version with the same prefix; a stable release skips pre-release tags.
- Then the release, the summary and outputsIt fills in or creates the release, writes the job summary, and hands the notes and JSON to later steps.
| Commit | Goes under |
|---|---|
| feat:, or “Add…”, “Support…” | Added |
| fix:, or “Fix…”, “Resolve…” | Fixed |
| perf:, refactor:, or “Update…”, “Improve…” | Changed |
| “Deprecate…” · “Remove…”, “Drop…” | Deprecated · Removed |
| feat!: or a BREAKING CHANGE: footer | Breaking changes, footer as migration steps |
| chore:, ci:, test:, release commits, branch merges | Left out |
Pull request merges use the pull request title and number. Change what's left out with exclude-types.
Want to see what it will find in your changelog? Paste it into the free converter: same parser, results in your browser.
Reference
Inputs and outputs
Every input is optional. Outputs work in later steps as steps.<id>.outputs.<name>.
Inputs
- github-token
- Token for reading tags and writing the release. The job needs contents: write to update releases.Default:
${{ github.token }} - tag
- Tag to write notes for. Defaults to the published release's tag or the pushed tag; a manual run without one uses the newest changelog version.Default:
— - version
- Version to look up in the changelog, for tags that don't contain one. Defaults to the version in the tag.Default:
— - changelog-path
- Path to the changelog, relative to the repository root.Default:
CHANGELOG.md - source
- auto uses the changelog section and falls back to commits; changelog fails without a section; commits always uses commits.Default:
auto - previous-tag
- Where the commit range and compare link start. Defaults to the highest earlier version with the same tag prefix.Default:
— - exclude-types
- Conventional Commits types left out of commit notes. Breaking commits are always kept; none keeps everything.Default:
chore,ci,build,test,tests,style - max-commits
- Most commits read for commit notes (1–1000).Default:
250 - style
- clean puts breaking changes first and lists each change once; original keeps the section as written.Default:
clean - update-release
- Write the notes into the GitHub release. false only sets outputs and the job summary.Default:
true - create-release
- Create the release when the tag exists but has no release yet. Never creates tags.Default:
true - body-mode
- Text already in the release: replace, append, prepend, or keep (only fill an empty body).Default:
replace - summary
- Write the notes to the job summary.Default:
true - compare-link
- End with a “Full changelog” link from the previous tag to this one.Default:
true
Outputs
- notes
- The release notes as Markdown.
- notes-file
- Path to a file with the notes, e.g. for gh release create --notes-file.
- json
- The release as JSON in LogPulse's machine-readable shape: version, date, breaking changes with migration steps, deprecations, changes.
- source
- changelog or commits.
- tag
- The tag the notes are for.
- version
- The version, without a leading v.
- previous-tag
- Where the commit range and compare link start.
- has-breaking-changes
- true or false.
- breaking-change-count
- Number of breaking changes.
- deprecation-count
- Number of deprecations.
- release-id
- ID of the release that was updated or created.
- release-url
- URL of that release.
- release-action
- updated, created, unchanged or skipped.
Private by design
Runs in your workflow. Talks only to GitHub.
Nothing leaves your runner
No LogPulse account, API key, analytics or telemetry. The only network calls go to GitHub's API, with the token you give it.
No dependencies at run time
One compiled file with no third-party packages, unminified so you can read it on GitHub. MIT licensed.
Never touches your tags
It edits or creates the release for a tag that exists. With update-release: false it only writes outputs.
Questions