Engineering

A changelog check for GitHub Actions

5 min read

Every team that keeps a changelog by hand has had the same conversation after the same incident: a release shipped with no entry, someone asks why, and the honest answer is that the person who would have written it was moving fast and the changelog step lived only in memory. Changelog automation covers what a pipeline can safely automate and what still needs a person; a changelog check in CI is the other half of that problem, because automating the writing does not help if nobody is required to trigger it in the first place. GitHub Actions is where most teams already run their pull request checks, so that’s where this one lives too.

Why does “we ask people to add an entry” fail on a predictable schedule?

Because it competes with everything else in a pull request for attention, and it is the one part with no immediate consequence for skipping it. Tests fail loudly and block the merge. A missing changelog entry blocks nothing, so it loses to deadline pressure the moment someone is in a hurry, which in practice is most of the time. A policy enforced by memory degrades at exactly the rate you would expect: fine for the first few weeks after everyone agrees to it, then quietly abandoned once the person who cared about it goes on vacation or moves teams.

What does a CI check for a changelog entry actually verify?

Not the quality of the writing, only that an entry exists and is well-formed, which is the right scope for a changelog check that runs in CI rather than in a person’s head. A common shape: the check looks at the diff for the PR, and requires either a new file in a changeset directory (the pattern Changesets and similar tools use) or a modified line in a changelog file, and fails the build if neither exists. The review of what the entry actually says still happens where it always did, in code review, because that judgment does not belong in a script.

What the CI check verifiesWhat it does not verify
A changeset file or changelog line exists in the diffWhether the wording is clear
The entry references the right package, in a monorepoWhether the change deserves a changelog entry at all
The file is syntactically valid (front matter, JSON shape)Whether the entry is honest about impact

Does every PR need one, or are some changes exempt?

Some are exempt, and the exemption list is where most of these systems actually get built or abandoned. A dependency bump with no user-visible effect, a test-only change, an internal refactor with no behavior change: none of these should force a contributor to invent a changelog entry for something nobody reading the changelog cares about. The working pattern is a label or a flag a contributor can apply (no-changelog-needed) that satisfies the CI check without a file, reviewed by whoever approves the PR, so the exemption itself goes through the same scrutiny as an entry would.

What happens to legitimate exceptions, like an urgent hotfix?

The gate belongs on the merge, not the deploy: a hotfix under real time pressure can merge with a placeholder entry or a follow-up ticket, provided the CI check is satisfied by intent rather than only by a finished paragraph; some teams accept a one-line stub that a maintainer polishes before the next release cut. What the gate should never allow is silently skipping the step, because a stub that gets forgotten is a smaller failure than an entry that never existed at all, and a stub at least leaves a trace someone can find later.

# .github/workflows/changelog-check.yml
on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
jobs:
  changelog:
    if: >-
      !contains(github.event.pull_request.labels.*.name,
      'no-changelog-needed')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # the diff needs the base branch
      - name: Require changelog entry
        run: |
          base="origin/${{ github.base_ref }}"
          if ! git diff --name-only "$base"...HEAD \
              | grep -q '^\.changeset/'; then
            echo "No changeset. Add one, or have a maintainer"
            echo "apply the no-changelog-needed label."
            exit 1
          fi

How do you know the check itself is correct before it starts blocking real PRs?

Open a scratch pull request against a throwaway branch first: one with a changeset, one without, and one carrying the exemption label, and confirm all three get the outcome you expect before the check applies to anyone else’s work. A changelog check that fails open, passing every PR because a condition was written backwards, is worse than having no check, because it looks like coverage that isn’t there. workflow_dispatch on the same file, run manually against a couple of recent merged PRs, catches most of these mistakes without needing a live pull request at all.

Does the same idea work outside GitHub Actions?

The shape carries over, only the syntax changes. GitLab CI expresses the same rule as a job rules block checking $CI_MERGE_REQUEST_LABELS instead of a GitHub Actions if, and a required merge request approval can substitute for the exemption review step. The check this article describes is GitHub Actions because that’s the platform most teams reading it are already on, but the underlying requirement, a machine-checked gate rather than an asked-for convention, is the same everywhere CI runs before a merge.

Does this work the same way in a monorepo?

It needs one more piece: which package the entry is for. Monorepo changelogs covers why a single repo-wide file stops working once packages release independently; the CI check inherits that same requirement; a changeset that does not name a package is not useful evidence that the right changelog will update, only that some file changed somewhere in the diff. Tools built for this (Changesets is the common one in the JavaScript ecosystem) ask the contributor to pick the affected package and a semver bump at the same time the changeset is created, so the CI check gets both pieces for free instead of inferring them later.

FAQ

Should the CI check block the merge, or just warn? Block. A warning is functionally identical to asking nicely, which is the thing that already failed. The exemption label exists precisely so a genuine warning-only case still has a legitimate path through the same hard gate.

Who reviews whether an exemption label was applied correctly? Whoever approves the pull request, as part of the same review they are already doing. The label should never be self-applied and unreviewed, or it becomes the same silent bypass the gate was built to close.

Does enforcing this in CI replace the need for a changelog automation pipeline? No, it feeds one. Changelog automation covers turning structured entries into a page, a feed and an email; the CI check is what guarantees those structured entries exist to automate in the first place.

What is the smallest version of this worth building first? A single check that fails if no file changed under a designated changelog directory, with one exemption label. Package-level routing and semver inference for a monorepo can come later; the core habit, an entry exists or someone explicitly said it does not need one, is the part worth having from day one.


The technical claims in this article have not been independently reviewed. If something here is wrong, tell us and we will correct it.

Related on changeloop: Changelog tools compared, Changelog generator

changeloop
The team building a closed-loop changelog. Your users ask, your team ships, the person who asked gets told.