Open source changelogs: CHANGELOG.md, GitHub Releases, or both?

Open source changelogs have a stranger readership than product changelogs. Almost nobody visits your repo to browse them. Instead they get read at three high-stakes moments: when a dependabot PR bumps your version and someone has 30 seconds to decide whether merging it will break production; when an upgrade already went wrong and someone is hunting for what changed; and when a potential adopter checks whether the project is alive. This guide covers where the changelog should live, how to keep it from becoming a merge-conflict machine, and what those three readers actually need from it.

The dependabot reader is your main reader

Package managers changed who reads changelogs. Most of your users never chose a version β€” a bot proposed one, and a human glanced at the diff. Dependabot and Renovate embed release notes directly in the bump PR, which means your changelog is read inside someone else's code review, compressed into a scroll-past. Write for that:

  • Breaking changes first, loud, every time. A BREAKING line at the top of the entry, with the one-line migration ("rename opts.retry to opts.retries"). If the reader has to open a linked issue to learn what breaks, most won't β€” they'll either merge blind or pin forever.
  • Say when there's nothing to fear. "No breaking changes. Safe to bump." is the most merge-encouraging sentence in open source, and almost nobody writes it.
  • Name the bugs you fixed by symptom, so the reader who hit one recognizes it: "fixed ETIMEDOUT on keep-alive connections idle > 60s" beats "fixed socket handling".

CHANGELOG.md vs GitHub Releases

Both are common; they have different physics.

  • CHANGELOG.md travels with the code: it's in the tarball, in vendored copies, in mirrors, readable offline and greppable with the tools you already have. It survives a platform move. But it only updates when someone edits it, and nobody is notified when it changes.
  • GitHub Releases come with distribution: watchers get notified, there's an Atom feed, and β€” decisive for the dependabot reader β€” release notes are what the bump PR embeds. But they're platform-locked, invisible in the tarball, and editable history (a release body can be quietly rewritten; a committed file can't without a trace).

The wrong answer is maintaining both by hand β€” they drift within three releases. The right answer is one canonical source, mechanically mirrored: write the entry once and let the release script paste it into the other surface. Which one is canonical matters less than that only one is.

The merge-conflict problem

A shared CHANGELOG.md with an [Unreleased] section has a famous failure mode: every PR appends to the same lines, so every PR conflicts with every other PR. Projects solve it one of three ways:

  • Fragment files. Each PR adds its own small file (Changesets for JS monorepos, towncrier in the Python world); the release step compiles fragments into the changelog and deletes them. No shared lines, no conflicts, and the entry is written while the author still remembers what the change does.
  • Label-driven drafts. Release Drafter accumulates merged PR titles into a draft release, sorted by label. Zero contributor friction β€” but PR titles are written to pass review, not to inform users, so budget maintainer editing time before publish.
  • Maintainer writes at release time. Fine for small projects with an engaged maintainer; it produces the best prose and the least process. It fails when the release is big enough that nobody remembers everything β€” which is exactly when the changelog matters most.

Credit is part of the changelog

Contributors get paid in visibility. "Thanks to @name for their first contribution" costs one line and is, for many people, the artifact they link from their profile for years. If a release includes outside contributions, credit them in the entry β€” by handle, next to the change, not in a bulk list at the bottom. GitHub's auto-generated notes do this mechanically; keep that part even if you rewrite everything else.

Anti-patterns

  • "See commit history." That's not a changelog, it's homework. The reader deciding on a dependency bump will not read 300 commits; they'll just not upgrade.
  • Publishing the raw commit dump as release notes. Auto-generation is a fine draft (we wrote a whole guide on doing it well), but "chore(deps): bump lodash" Γ— 40 tells users nothing.
  • The stale file. A CHANGELOG.md whose last entry is two majors old is worse than none β€” it actively signals abandonment to the adopter doing due diligence. If you've moved to Releases, replace the file's contents with a link.
  • Empty release bodies. A tag with no notes embeds as nothing in the bump PR. Even two lines β€” what changed, whether it breaks β€” beat the void.

Where Wakelog fits

Wakelog gives an open source project a hosted changelog page with feeds, without changing where your canonical source lives. Paste a github.com/owner/repo URL and it imports your published GitHub Releases (or paste your CHANGELOG.md β€” the parser handles keep-a-changelog and most real-world variants); point a release webhook at it and every future release auto-posts. You get a clean public page, RSS + JSON feeds for the humans who want to follow along without watching the repo, a README badge showing when you last shipped, and a changelog.md export so the exit is one GET request.

Host your project's changelog free   Preview yours: paste a repo URL β†’

Related guides

Last updated 2026-07-27 Β· All guides