This GitHub Action extracts the body of one release section for a given tag from CHANGELOG.md.
For a provided tag (for example v1.2.3) it finds the matching changelog section and writes only the section body to a target file (or prints to stdout in standalone mode without --release-notes).
It keeps release publishing workflows simple and deterministic when your release body should come directly from CHANGELOG.md.
- Pure Bash implementation with minimal dependencies
- Fast startup: no
npm/pipinstall step during workflow execution - Lower supply-chain and maintenance overhead: no runtime pinning, lockfiles, or dependency CVEs
- Easier security audit: all logic lives in a small, readable script
- Covered by automated tests (
./tests/run) and CI - Works on
ubuntu-slim, which can help reduce runner costs: https://docs.github.com/en/actions/reference/runners/github-hosted-runners - Can be used both as a GitHub Action and as a standalone script
- Released under the MIT License: a short and simple permissive license
- Documented security policy: SECURITY.md
- Add the action to your workflow (see "Example workflow" below).
- Pass the release
tag. - Optionally change
changelogandrelease_notes. - Use the generated file (
release_notes) as release body in the next step.
| Name | Required | Default | Description |
|---|---|---|---|
tag |
Yes | - | Release tag to extract, e.g. v1.2.3 |
changelog |
No | CHANGELOG.md |
Path to changelog file |
release_notes |
No | release-notes.md |
Output file path for extracted release notes |
| Name | Description |
|---|---|
release_notes |
Path to file with extracted release notes (same value as inputs.release_notes) |
Run locally from this repo:
./release-notes-from-changelog \
--tag "v1.2.3" \
--changelog "CHANGELOG.md" \
--release-notes "release-notes.md"To print extracted section to stdout, omit --release-notes:
./release-notes-from-changelog \
--tag "v1.2.3" \
--changelog "CHANGELOG.md"Options map 1:1 to the action inputs.
You can also set them via env vars:
TAG, CHANGELOG, RELEASE_NOTES.
If both env vars and CLI options are provided, CLI options take precedence.
Accepted changelog headings for tag v1.2.3:
## [1.2.3] - 2026-02-19
## [v1.2.3] - 2026-02-19
## 1.2.3 - 2026-02-19
## v1.2.3 - 2026-02-19
Example action usage:
- uses: octivi/release-notes-from-changelog@v1
with:
tag: ${{ github.ref_name }}
changelog: CHANGELOG.md
release_notes: release-notes.mdRun the lightweight test suite (no external deps):
./tests/run- Expects release headings that start with
##and end with date inYYYY-MM-DD - Omits the matched
## ...release heading from generated notes (to avoid duplicating GitHub release titles) - Fails if no matching section exists for the tag
- Fails if multiple matching sections exist for the same tag
- Requires exactly one release section per tag in the changelog
release_notes/--release-notesmust point to a file path (not a directory)- Output directory for
release_notesmust already exist release_notes/--release-notesmust not point to the same file aschangelog
name: Release
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
jobs:
release:
name: Create GitHub Release
runs-on: ubuntu-slim
steps:
- uses: actions/checkout@v6
- name: Generate release notes
id: release_notes
uses: octivi/release-notes-from-changelog@v1
with:
tag: ${{ github.ref_name }}
changelog: CHANGELOG.md
release_notes: release-notes.md
- name: Publish release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
body_path: ${{ steps.release_notes.outputs.release_notes }}
generate_release_notes: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}If you run into issues, open a GitHub issue in this repository and include a minimal reproduction (sample changelog + workflow snippet).
If you are interested in other GitHub Actions we build, see:
octivi/update-copyright-year- Updates the copyright year in file headers across your repositoryoctivi/update-securitytxt-expires- Updates theExpiresfield insecurity.txtfiles to a future date so published security contact metadata stays currentoctivi/cloudflare-cache-purge- Purges Cloudflare cache via Cloudflare API
Maintained by the Octivi DevOps team. Contributions are welcome via pull requests.
Built with Octivi Bash Boilerplate.