Release notes are one of those small artifacts that quietly influence trust. Customers use them to understand what changed. Support teams use them to answer “when did this ship?” Engineers use them to debug regressions and to verify what actually made it into a release.
The problem is that release notes usually become a last-minute chore. When they’re rushed, they get vague (“Various improvements”), incomplete (missing fixes), or misleading (claiming a feature that was partially shipped). AI can help, but only if the workflow is designed to limit hallucinations and force the model to work from concrete, reviewable inputs.
This post walks through an evergreen, low-drama approach: use GitHub as the event source, constrain what AI is allowed to write, and add a few fast checks so the output is accurate enough to publish with minimal human time.
Why release notes break (and what “good” looks like)
Release notes break for predictable reasons: the “real” changes are scattered across commits, pull requests, and chat messages; there isn’t a consistent audience; and the person writing them is often doing it under deadline pressure.
Before you automate, define what “good” means for your product. For most teams, good release notes are:
- Accurate: nothing claimed that did not ship.
- Complete enough: major user-facing changes and important fixes are included, but not every tiny refactor.
- Readable: grouped by theme (Added/Changed/Fixed), written in plain language, and free of internal jargon.
- Traceable: each bullet maps back to a PR or issue so someone can verify details quickly.
AI is best used as a formatter and summarizer, not as a historian. The goal isn’t to make release notes “automatic”; it’s to make them repeatable with less manual typing and fewer omissions.
Choose your source of truth: tags, milestones, or PRs
Start by choosing a single “release boundary” that defines what’s included. If you don’t, AI will summarize whatever you give it, and you’ll still end up arguing about what shipped.
Common options in GitHub:
- Git tags: a release is everything between two tags (for example,
v1.8.0..v1.9.0). This is great when tagging is consistent. - Milestones: a release is everything assigned to a milestone. This works well when product management is disciplined about milestone hygiene.
- Merged PRs: a release is all PRs merged into a branch between two dates or between tags. This is practical when PR titles are meaningful.
Pick one, document it in a short internal note, and keep it stable. Changing boundaries every release makes automation fragile and invites inconsistencies.
Use labels to pre-sort content
Labels are the easiest way to help AI produce organized notes. A simple label system is enough:
type:featuretype:fixtype:maintenance(optional: usually excluded from public notes)area:billing,area:api,area:ui(optional, useful for grouping)
Even if you can’t label everything perfectly, having labels on the top 20% of impactful PRs dramatically improves consistency.
Structure the inputs so AI can’t invent details
The main risk with AI-authored release notes is invented specificity: wrong settings, incorrect feature names, or claims about behavior that doesn’t match reality. The antidote is to feed the model a constrained, structured “release bundle” and explicitly forbid it from adding details that aren’t present.
A practical release bundle contains:
- Release version and date (for the header).
- List of PRs with: PR number, title, labels, and a short human-authored “release note hint” when available.
- List of linked issues (optional) to help identify user-facing context.
- Exclusions: anything labeled
type:maintenanceorinternalthat should not appear publicly.
If you already encourage engineers to write a “release note hint” in the PR description (one sentence max), you’ll get higher-quality output than if you rely on commit messages. If you don’t have that habit yet, start with PR titles and improve over time.
Here’s a conceptual structure for the bundle you hand to the model (not code, just the shape of the data):
{
"release": { "version": "vX.Y.Z", "range": "vX.Y.(Z-1)..vX.Y.Z" },
"prs": [
{ "id": 1234, "title": "Add CSV export to invoices", "labels": ["type:feature","area:billing"], "hint": "Users can export invoices as CSV from the invoice list." },
{ "id": 1255, "title": "Fix timezone parsing in reports", "labels": ["type:fix","area:reports"], "hint": "" }
],
"excludeLabels": ["type:maintenance","internal"]
}
This structure makes it harder for the model to “creatively interpret” what shipped. It also makes it easier to spot-check output, because every bullet should trace back to a single PR entry.
A simple drafting workflow that scales
You don’t need an elaborate system. A stable workflow usually has four steps, and you can run it manually at first before automating it with CI:
- Collect: assemble the release bundle from GitHub (tag range, milestone, or PR list).
- Draft: ask AI to turn the bundle into release notes using a strict format.
- Verify: run quick checks and do a human skim for risk areas.
- Publish: commit the notes to your repo, paste into your CMS, or attach to a GitHub Release.
The most important part is the draft instruction. Don’t just say “write release notes.” Tell the model what it must do and what it must not do.
Prompting for consistency (and fewer arguments)
A useful instruction set includes:
- Audience: “Write for end users and support; avoid internal implementation details.”
- Allowed sources: “Use only the provided PR titles and hints.”
- Grouping: “Produce sections: Added, Changed, Fixed.”
- Traceability: “Append (PR #1234) to each bullet.”
- Style: “Use sentence case, keep bullets under 140 characters where possible.”
- Hard rule: “If input is ambiguous, write a generic bullet or omit it; do not guess.”
This feels restrictive, but it’s what makes the automation dependable. In practice, the model becomes a fast editor that turns raw engineering artifacts into consistent prose.
Key Takeaways
- Define a single release boundary (tags, milestone, or merged PRs) and stick to it.
- Give AI a structured “release bundle” and forbid it from adding unverified details.
- Require traceability: every bullet should map to a PR number.
- Add lightweight checks (counts, label exclusions, risky wording) before publishing.
- Start manual, then automate once the format is stable and trusted.
Quality control: checks that catch the usual failures
Quality control doesn’t need to be heavy to be effective. The goal is to catch the few failure modes that create confusion or erode trust.
Fast mechanical checks
- PR count reconciliation: confirm the number of included PRs roughly matches the number of bullets (after excluding maintenance/internal labels).
- Excluded labels are absent: scan for any PR IDs that were marked for exclusion.
- Formatting consistency: verify the expected headings exist (Added/Changed/Fixed) even if some are empty.
- Traceability present: every bullet has a PR reference.
A quick human semantic review
Assign a short, repeatable checklist to whoever does the final skim (often the release manager, tech lead, or on-call engineer):
- Risky claims: remove absolutes like “always,” “never,” or “fully resolved” unless you’re confident.
- User impact clarity: ensure each bullet says what the user can do or what behavior changed.
- Terminology: confirm feature names match what users see in the UI.
- Privacy/safety: confirm no sensitive internal detail is exposed (stack traces, internal hostnames, customer names).
If a bullet seems questionable, don’t debate it—click through the PR. The “traceability” rule makes this fast.
Rollout and maintenance: keep it boring
The biggest long-term risk isn’t the model; it’s process drift. A few habits keep the system stable:
- Make PR titles do more work: treat the PR title as a user-facing summary when possible. This reduces the need for the model to “interpret.”
- Add a one-line release hint field: even a simple convention (“Release note:” in the PR description) improves results.
- Keep a changelog taxonomy small: Added/Changed/Fixed is usually enough. More categories create bikeshedding.
- Version your format: if you need to change the structure, note “Release notes format v2” internally so you can explain differences later.
- Decide where notes live: choose one canonical location (repo changelog, GitHub Releases, or your CMS) and mirror elsewhere if needed.
Once the workflow is stable, automation becomes straightforward: run the “collect → draft → verify” steps in your existing release process, and only publish after a human approves the final text.
Conclusion
AI can make release notes faster and more consistent, but only when the workflow treats GitHub as the source of truth and forces traceability. Start with structured inputs and a strict format, add a couple of lightweight checks, and keep the process boring. The result is release notes that are easier to trust, easier to maintain, and easier to publish on a regular cadence.
FAQ
Do I need to fully automate this in CI/CD?
No. Many teams get most of the benefit by running a manual “generate draft” step and doing a quick review. Automate only after the format and checks are stable.
What if our PR titles are messy?
Start by improving titles for high-impact changes and add a one-sentence release hint in the PR description. You don’t need perfection; you need enough signal for the important items.
How do we prevent sensitive information from leaking into public notes?
Use exclusion labels (for example, internal) and add a review checklist item specifically for sensitive details. Also instruct the model to use only the provided bundle, not to infer or expand technical specifics.
Should we include every fix and refactor?
Usually not. Include user-facing fixes and anything support teams need to know. Keep refactors and internal maintenance in a separate internal changelog if they’re valuable to engineers.
Where should release notes live?
Pick one canonical home and keep it consistent. A repo changelog works well for internal traceability; a CMS post works well for customer communication. The key is that the workflow produces the same structured output regardless of the publishing destination.