In GitHub, how do you control what automatically generated release notes contain?
answer
- Built from merged pull requests, not commits
- One YAML file on the default branch
- Categories route by label, first match wins
- A wildcard entry catches the unlabelled
- Preview endpoint exists before you publish
basics
~20 sAsk GitHub to generate notes (generate_release_notes on the API, --generate-notes on the CLI) and shape the output with a .github/release.yml file, whose changelog block groups merged pull requests into categories by label and excludes chosen labels or authors.
solid answer
~40 sGenerated notes are built from the **merged pull requests between the previous release's tag and this one**, so what you get is a function of your pull-request titles, labels and authors — not of your commit messages. Turn it on with `generate_release_notes: true` on `POST /repos/{owner}/{repo}/releases`, `--generate-notes` on `gh release create`, or the *Generate release notes* button; there is also `POST /repos/{owner}/{repo}/releases/generate-notes` to preview the body without creating anything. Shaping happens in `.github/release.yml` on the default branch. Under `changelog:` you set `exclude:` with `labels:` and `authors:` lists, and `categories:` — each with a `title` and the `labels` that route into it. A catch-all category using `labels: ['*']` collects anything unmatched. GitHub also adds new-contributor credits and a full-changelog compare link. The corollary is cultural: your changelog quality is now your PR-title and labelling discipline.
code
yaml · 19 lineschangelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot
categories:
- title: Breaking changes
labels:
- breaking-change
- title: New features
labels:
- enhancement
- title: Bug fixes
labels:
- bug
- title: Other changes
labels:
- "*"go deeper
Know that GitHub can generate release notes for you, listing the pull requests merged since the last release, and that a .github/release.yml file shapes the result.
Explain the changelog block — exclude.labels, exclude.authors, and ordered categories routed by label with a '*' catch-all — and that generation reads merged pull requests rather than commits.
Show the operational edges: overriding the previous tag on maintenance branches, previewing with the generate-notes endpoint, and combining generated inventory with a hand-written breaking-change lead.
Treat the changelog as a contract with consumers and design upstream for it: labelling and pull-request-title discipline at review time is what makes any automated changelog trustworthy.
## What generation actually reads GitHub builds notes from the **pull requests merged between two tags** — the previous release and the one being created. Each line is a pull request *title*, its number, and its author. Commits merged directly to the branch without a pull request do not appear, and neither do commit message bodies. That single fact reframes the whole feature: the changelog is assembled from metadata that reviewers already curate, so the way to improve it is to improve pull-request titles rather than to write notes by hand afterwards. The previous release is chosen automatically, and can be overridden — `previous_tag_name` on the API, `--notes-start-tag` on the CLI. That override is what makes generation usable on a maintenance branch, where "the previous release" chronologically is a newer major that has nothing to do with your line. ## Turning it on - API: `POST /repos/{owner}/{repo}/releases` with `"generate_release_notes": true`, which fills `body` for you. Anything you pass in `body` is kept and the generated content is appended. - CLI: `gh release create v2.5.0 --generate-notes`, or `--notes-file` to supply your own. - Preview without side effects: `POST /repos/{owner}/{repo}/releases/generate-notes` returns the name and body it *would* produce, given `tag_name` and optionally `previous_tag_name` and `configuration_file_path`. The preview endpoint is underused and genuinely handy — it lets a workflow post the proposed notes on a release-prep pull request before anything is published. ## Shaping with .github/release.yml The configuration file lives at `.github/release.yml` on the default branch, under a single `changelog:` key: - **`exclude.labels`** — pull requests carrying any of these labels are dropped entirely. The standard use is `ignore-for-release` for chores and internal refactors. - **`exclude.authors`** — drop by author, most often a bot account so that dependency-bump noise does not swamp the human changes. - **`categories`** — an ordered list. Each entry has a `title` (the heading rendered in the notes), a `labels` list that routes matching pull requests into it, and optionally its own `exclude`. Order matters: the first matching category wins, so put narrow categories such as *Breaking changes* above broad ones. - **A catch-all** — a final category whose `labels` is `['*']` collects everything unmatched, so nothing silently vanishes. Omit it and unlabelled pull requests disappear from the notes without warning, which is a quiet way to lose a change from the record. Because routing is label-driven, categories only work if labelling is real. Pair the file with a labelling habit enforced at review time, or accept that most entries land in the catch-all. ## What GitHub adds on its own Generated notes include a **New contributors** section crediting first-time merged authors, and a **Full changelog** link comparing the previous tag to this one. Both come free and both are the sort of thing hand-written notes forget. ## Generated versus hand-written The honest position is that generated notes are a *complete inventory* and a good changelog is a *narrative*. Inventory answers "what changed"; narrative answers "should I upgrade, and what will hurt". The pattern that works is to let generation produce the inventory and hand-write a short lead paragraph above it — breaking changes, migration steps, the one bug everybody hit. Passing `body` alongside `generate_release_notes: true` does exactly that: your prose first, the generated list beneath it. ## Failure modes worth naming - **Squash-merge titles become changelog lines.** If your squash commit title defaults to the pull request title, sloppy titles are permanent. Reviewers should fix the title before merging, not after. - **A missing catch-all category** silently drops unlabelled work. - **The wrong previous tag** on a maintenance release produces a changelog spanning an unrelated major. Set it explicitly on release branches. - **`release.yml` on a feature branch** does nothing; it is read from the default branch.
- A merged pull request is missing from the generated notes. What are the likely causes?It carries a label listed under `exclude.labels`, its author is in `exclude.authors`, it fell outside the tag range because the previous tag was resolved differently than you expected, or your categories have no wildcard entry so unlabelled pull requests are dropped. Work is also invisible if it landed as a direct push rather than through a pull request.
- How do you generate sensible notes for a release on a maintenance branch?Set the starting point explicitly — `previous_tag_name` on the API or `--notes-start-tag` on the CLI — pointing at the previous release on that same line. Left to itself the comparison can span an unrelated newer major and produce a changelog full of changes that are not in your branch at all.
- Should generated notes replace hand-written release notes entirely?They replace the inventory, not the narrative. Generation lists every merged pull request accurately, which humans do badly, but it cannot say which change breaks callers or what a migration requires. Pass your own `body` alongside `generate_release_notes` so a short prose lead sits above the generated list.
- Why do pull request titles suddenly matter more once notes are generated?Each generated line is a pull request title, so the title is the changelog entry a user reads months later. With squash merges the title also becomes the commit subject on the default branch, meaning one careless phrase lands in both records. Fixing titles at review time is far cheaper than editing published notes.
saying these in an interview costs you the question
- Thinks generated notes come from commit messages
- Omits a wildcard category and loses unlabelled work
- Assumes release.yml works from a feature branch
- Lets the previous tag default on a maintenance branch
- Believes generation removes the need for migration notes