AI Changelog Generation — From Commits to a Release Note Humans Read

Turn git log into release notes that aren't just a dump of commit messages.

Most “AI, write the changelog” sessions produce a bulleted dump of commit messages that nobody actually reads. The reason is simple: git log is written for the author at commit time, and a release note is written for the reader at release time. Different audiences, different framing. This workflow gets you from raw commits to a two-tier release note (one section users will read, one section developers will read) without losing the technical accuracy that makes the note trustworthy.

What this covers

A reproducible workflow for generating release notes with AI from real inputs — git log v1.2.0..HEAD --oneline, merged PR titles, closed issue numbers — and a review pass that catches the things AI tends to miss (user-visible behavior changes hidden behind a “refactor” commit, breaking API changes buried under a “chore” prefix).

Who this is for

Maintainers who cut releases weekly or biweekly, indie devs shipping product updates, and tech leads who own the release note but resent spending an hour reading 45 commits to write it.

When to reach for it

Releases with 20-100 commits where the surface area is large enough that no single person remembers everything that changed. Semver-disciplined repos where commit messages have at least loose convention (feat:, fix:, chore:). End-of-sprint release notes that go to a mixed audience — users, support, and engineering all reading the same doc.

When this is NOT the right tool

Security releases — the wording matters too much to delegate, and you do not want the model paraphrasing a CVE description. Bare-bones tags without commit discipline (one-line “wip” messages) — the AI has nothing to ground on and will invent. Tiny patch releases (1-3 commits) — write it yourself in 2 minutes.

Before you start

  • Pick the tag range. git log v1.2.0..HEAD --oneline is the canonical input. If you do not tag, use the last release SHA.
  • Pull PR titles and labels too — they often carry more user-facing intent than the commit messages. gh pr list --state merged --search "merged:>2026-05-01" works.
  • Decide the audience tiers in advance. Most projects need exactly two: “For users” (behavior changes, new features, breaking changes) and “For developers” (internal refactors, dependency bumps, infra). One tier is fine for libraries.
  • Have the previous changelog open. Tone, section order, and category names should match — readers learn your format and you should not break it without reason.

Step by step

  1. Generate the raw inputs. A typical mid-size release: 45 commits across 3 weeks.
    git log v1.2.0..HEAD --oneline > /tmp/commits.txt
    gh pr list --state merged --base main --search "merged:>=2026-05-01" \
      --json number,title,labels > /tmp/prs.json
  2. Paste both into one message. Tell the model the audience tiers and the section names you want (Added / Changed / Fixed / Removed, or whatever your project uses).
  3. Ask for grouping first, no prose. “Group these into Added / Changed / Fixed / Removed. One line per commit. Keep PR numbers.” This catches mis-categorized commits early and is cheap to review.
  4. Review the grouping. Move anything mis-bucketed. A “fix:” that is actually a feature gets moved up. A “refactor:” that changes a user-visible default gets moved to Changed.
  5. Ask the model to rewrite each line for the user-tier section. “Rewrite the Added / Changed / Removed sections for non-engineer readers. Skip the internal-refactor items.” This is the step that separates a release note from a commit dump.
  6. Ask for a separate developer-tier section with the internal items left intact. Dependency bumps, refactors, test-only changes go here.
  7. Read both sections out loud. If you trip on a sentence, rewrite it. Then check every breaking change is called out explicitly with a migration note.

A prompt that produces honest output

Here are 45 commits from v1.2.0..v1.3.0:

\{paste git log --oneline output\}

Here are the merged PR titles with labels:

\{paste gh pr list JSON\}

Produce a release note with two sections:

1. "For users" — only user-visible changes (new features, behavior
   changes, bug fixes that affect users, breaking changes with migration
   notes). Plain language. No internal jargon.
2. "For developers" — internal refactors, dependency bumps, infra,
   test-only changes, anything not user-visible.

Within each section, group as: Added / Changed / Fixed / Removed.
Keep PR numbers in parentheses.

Rules:
- Do NOT invent features. If a commit message is unclear, mark it
  "[unclear — needs review]" and I will check.
- Do NOT soften breaking changes. Call them out with a "Breaking:" prefix
  and a one-line migration note.
- Do NOT merge unrelated commits into one line.

Quality check

  • Every breaking change is labeled “Breaking:” with a migration note. AI tends to bury these — re-grep the diff for API changes and confirm each one shows up.
  • No invented features. Cross-check anything that sounds new against the actual PR. The model will sometimes elaborate a one-line commit into a paragraph that promises more than was shipped.
  • User-tier section has no internal jargon. No “refactored the FooService” — that belongs in the developer tier.
  • Counts roughly match. 45 commits in, somewhere between 20 and 35 lines out. If the AI gave you 12 lines, it collapsed too much.
  • Tone matches the previous changelog. Same person could have written both.

How to reuse this workflow

  • Save the two-tier prompt as a snippet. Same template works release to release; only the input changes.
  • Keep a “Breaking change checklist” — API removals, default changes, migration steps required. Re-check every release.
  • Build a tiny script that runs git log + gh pr list and dumps both into a single file ready to paste. Removes 90% of the friction.
  • After each release, note any mis-categorized items and add an example to the prompt for next time. The prompt gets sharper.

git log v1.2.0..HEAD + PR titles → AI groups into Added / Changed / Fixed / Removed → human reviews grouping → AI rewrites user tier in plain language → AI emits developer tier verbatim → human reads aloud → ship. For a 45-commit release this takes 20-30 minutes, vs 90 minutes by hand. For a 7-section note, expect 3-4 rounds of “this line is wrong” corrections.

Common mistakes

  • Pasting only git log without PR titles. Commits are written tersely; PR titles carry the intent. You lose half the signal.
  • Letting AI write the user-tier section in one shot, no grouping pass. Mis-categorized items survive into the final note.
  • Skipping the read-aloud pass. Awkward AI phrasing slips through and makes the whole note feel machine-generated.
  • Trusting the AI on breaking changes. It will soften “Removed X” into “Updated X.” Always re-verify each removal.
  • Using it on security releases. Wording matters too much; do those by hand.

FAQ

  • What if commit messages are bad?: AI can still produce a draft from PR titles + issue references, but quality drops fast. Long term, the answer is to invest in commit discipline (conventional commits, PR templates).
  • Can the AI tag PRs by section automatically?: Yes, if PRs carry labels (type:feature, type:fix). The labels are a stronger signal than the message. Use them when present.
  • Should I let it write the headline summary?: Draft yes, ship no. The one-sentence summary at the top of a release note is the part users actually read — write it yourself once the body is final.
  • What about per-commit attribution?: If your project credits contributors, ask the model to keep authors. git log --pretty='%h %an %s' gives you the input. Verify the names against the real list before publishing.

Tags: #AI coding #Workflow