Skip to content
Goatfied

workflows

Automating changelog generation from merged PRs

Automated changelog generation extracts PR metadata like titles, labels, and commit prefixes to build release notes without manual copy-pasting.

2026-09-168 min readBy Goatfied
Automating changelog generation from merged PRs

Most engineering teams maintain changelogs by hand: someone copy-pastes PR titles into a CHANGELOG.md file before each release, reformats them into categories, and tries to remember which ones matter to users. By the time you're shipping weekly or daily, this busywork becomes a tax on velocity—and the resulting changelog is usually incomplete or cryptic.

Automated changelog generation treats your merged pull requests as the source of truth. When you merge code, the system extracts structured data (title, labels, linked issues, conventional commit prefixes) and assembles a human-readable changelog without manual intervention. The tradeoff is obvious: automation only works if your PRs carry enough signal. Garbage in, garbage out. But once you've established the discipline, you get consistent, timely changelogs that scale with your release cadence.

Why PR metadata beats manual changelog editing

A changelog maintained in a separate document falls out of sync the moment someone forgets to update it. PRs, by contrast, already contain most of what you need: a description of what changed, why it changed, and often a link to the issue or ticket that prompted the work. If you've labeled PRs with bug, feature, or breaking-change, you've already done the categorization work.

The challenge is consistency. Automated systems rely on conventions: if half your team writes "fix stuff" as a PR title and the other half writes "fix: resolve edge case in webhook retry logic", only the second one will produce a useful changelog entry. This is where process meets tooling. You need both a convention (Conventional Commits is the de facto standard) and enforcement (a bot or CI gate that blocks merges if the title doesn't match the pattern).

Goatfied's agent loop surfaces these issues early. When the agent proposes a PR, it generates a title following your team's convention (often feat:, fix:, chore: prefixes). The validate step checks whether the title matches the required format before the PR is opened. If your repository requires a linked issue, the agent ensures one exists. This shifts quality control left, so you're not fixing malformed PRs after they've merged.

Structuring PR titles and labels for automation

Conventional Commits defines a simple schema: <type>(<scope>): <subject>. For example:


feat(api): add webhook retry with exponential backoff

fix(ui): correct timezone handling in date picker

chore(deps): upgrade typescript to 5.3.2

The type becomes the changelog category (Features, Bug Fixes, Chores). The scope adds context (which subsystem or module). The subject is the user-facing description. If you append a footer like BREAKING CHANGE: ... or use an exclamation mark (feat!:), the changelog generator can flag breaking changes prominently.

Labels offer a parallel signal. Many teams tag PRs with bug, enhancement, documentation, or dependencies. Changelog tools can map these to sections. The advantage of labels is flexibility: you can retroactively tag a PR without rewriting the title. The downside is they're often applied inconsistently unless you automate label assignment based on file paths or commit patterns.

A middle ground: use conventional commit prefixes as the primary signal and labels as a fallback or filter. For example, exclude all PRs labeled internal from the customer-facing changelog, even if they use feat: or fix:. This keeps internal refactors and tooling changes out of release notes without inventing a separate commit type.

Choosing between changelog-as-code and release note services

You can generate changelogs in two ways: scripts that run in CI and produce a Markdown file, or third-party services that integrate with GitHub/GitLab and publish release notes automatically.

Changelog-as-code tools like git-cliff, conventional-changelog, or changie parse commit history or PR metadata and render Markdown. They run as part of your release pipeline—typically after you tag a version—and commit the updated changelog to your repository. This approach keeps everything in source control. The tradeoff is you must maintain the configuration (regex patterns, templates, category mappings) and integrate the tool into your CI.

Release note services like Release Drafter or semantic-release watch your repository for merges, update a draft release in GitHub, and optionally publish it when you create a tag. They pull directly from PR metadata (title, labels, linked issues) rather than parsing commits. The benefit is less scripting; the downside is you're coupled to the platform's release UI and may need webhooks or GitHub Actions for customization.

For teams shipping continuously, changelog-as-code fits better. You can trigger generation on every merge to main, appending entries to an UNRELEASED section in CHANGELOG.md. When you cut a release, the script moves everything under UNRELEASED to a version heading and tags the commit. This gives you a living document that stays current without waiting for a formal release.

Filtering noise and handling contributor PRs

Not every PR belongs in a user-facing changelog. Dependency bumps, CI config tweaks, and internal refactors clutter the output. Most generators let you exclude PRs by type (chore:, ci:, test:) or label (internal, dependencies). The trick is calibrating the filter: some dependency updates introduce breaking changes or require migration steps, so a blanket exclusion can hide important information.

One heuristic: include dependency PRs only if they have a breaking-change label or mention a major version bump in the title. Otherwise, roll them into a single "Dependencies updated" line or omit them entirely. For internal refactors, consider a separate "Internal changes" section at the bottom of the changelog, collapsed by default in web renderings. This preserves the audit trail without overwhelming users.

External contributor PRs deserve special handling. A first-time contributor might not know your commit conventions, and rejecting their PR because the title lacks a prefix creates friction. Many teams use a bot (like conventional-pr) that validates titles but allows maintainers to override by adding a label like skip-changelog-check. When the PR merges, a GitHub Action rewrites the merge commit message to conform to the convention, ensuring the changelog stays clean without burdening contributors.

Integrating changelog generation with CI and release workflows

A typical CI pipeline for automated changelog generation looks like this:

1. On PR open: A bot validates the title format and labels. If validation fails, the bot comments with guidance and blocks merge until fixed.

2. On merge to main: A workflow runs the changelog generator, appending the new entry to an UNRELEASED section in CHANGELOG.md and committing the change.

3. On release tag: The workflow moves UNRELEASED entries to a version heading (e.g., ## [1.3.0] - 2025-01-15), creates a GitHub Release with the same content, and pushes the updated file.

For example, using git-cliff in GitHub Actions:


name: Update Changelog

on:

  push:

    branches: [main]

jobs:

  changelog:

    runs-on: ubuntu-latest

    steps:

      - uses: actions/checkout@v4

        with:

          fetch-depth: 0

      - run: |

          curl -LO https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-linux-amd64.tar.gz

          tar -xzf git-cliff-linux-amd64.tar.gz

          ./git-cliff --unreleased --prepend CHANGELOG.md

      - run: |

          git config user.name "github-actions[bot]"

          git config user.email "github-actions[bot]@users.noreply.github.com"

          git add CHANGELOG.md

          git commit -m "chore: update changelog [skip ci]"

          git push

The [skip ci] tag prevents an infinite loop. When you tag a release, a separate job moves the UNRELEASED section under the version number and publishes the release notes.

Goatfied's workflow automation can orchestrate this end-to-end. Because the agent runs in a constrained environment with compile and lint gates, you can define a workflow that validates PR titles, generates the changelog diff, and verifies the Markdown syntax before committing. If the changelog update fails (for example, a malformed commit sneaks through), the agent retries with corrected metadata rather than silently producing a broken file. This "validate before merge" model catches errors before they propagate to production changelogs.

Migrating from manual changelogs to automated workflows

If you're inheriting a hand-maintained CHANGELOG.md, the first step is to establish a baseline. Run your chosen generator against recent history (say, the last 10 merged PRs) and compare the output to your existing changelog. You'll likely find discrepancies: missing entries, incorrect categories, or titles that don't match the actual changes. Use this exercise to identify gaps in your PR discipline.

Start enforcement gradually. Add the PR title validation bot but make it advisory for the first sprint—it comments but doesn't block. Let the team acclimate to the convention. After a week or two, switch the bot to blocking mode. Run the changelog generator on a branch and review the output in a PR. Once the quality threshold is acceptable, wire it into CI and archive the manual process.

Preserve historical entries. Most generators support a ## [Unreleased] heading followed by version sections. Copy your existing changelog below the unreleased line. The generator will only touch new entries, leaving the old ones intact. This avoids a jarring cutover and maintains the audit trail.

Related posts

Automating changelog generation from merged PRs | Goatfied Blog