Changelog guard
A GitHub Action that fails a pull request which changes a project and writes nothing down about it.
Why it exists
What that means in a diff: the project's changelog has to carry a ## heading naming the version its manifest declares. A change a reader can observe moves that version, so the heading is a new one, and writing what goes under it is the pull request's job rather than the release's.
A fix shipped with nothing written down loses the reasoning while somebody still remembers it, and the reader who needs it is on the previous version deciding whether this one affects them. Written at release time instead, the entry is written by whoever cut the release, from a diff, about work they may not have done.
Adding it
name: Validate
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: releasetools/actions/changelog-guard@v0
A group that names no changelog owes none, so the check asks nothing until one does. Name the file in .releasetools.yaml at the repository root:
projects:
- path:
- packages/api
- packages/web
manifest: package.json
changelog: CHANGELOG.md
changelog is a name inside each project's own directory, and every project is named rather than matched. ignore-files and case-sensitive decide what counts as a project changing, and the versions guard documents them. Both guards read the same file.
What counts as a heading
A ## carrying the version the project's manifest now declares.
| heading | against version 0.2.0 |
|---|---|
## 0.2.0 | passes |
## 0.2.0 - 2026-09-19 | passes, which is what the release notes plugin writes |
## [0.2.0] - 2026-09-19 | passes, which is what Keep a Changelog writes |
## v0.2.0 | passes |
## 0.2.0-rc1 | fails |
### 0.2.0 | fails |
Changelog not updated
packages/api/CHANGELOG.md has no section for 2.1.0. Add one above the
older releases: what changed, and the reason not to act that a later
change cannot get from the diff.
A project with no changelog file at all is told to start one.
Why it is not enough on its own
A project whose version never moved is satisfied by the section written for its last release, because that section carries the version the manifest still claims. Catching that is the versions guard's job, and it is why the two ship together.
Letting one pull request through
Label it skip-changelog-guard.
What it reports
One line per project whose changelog carries the version it claims, then a verdict:
packages/api 0.2.1
Every changed project recorded what changed
Failures arrive as annotations on the pull request, titled Changelog not updated.
Inputs
| input | default | |
|---|---|---|
base | the pull request's base | the ref to compare against, resolved to where the branch forked from it. Read from the event on a pull request, and has to be set anywhere else |
The full reference is in the action's guide.