← Blog

How to Build a Claude-Powered Release Notes Generator

2026-11-02 · 5 min read · SubToAPI Team

If you're searching for how to build a Claude-powered release notes generator, you're probably tired of writing release notes by hand after every ship — or tired of changelogs that are just a wall of commit messages nobody outside your team can parse. The fix is to feed your raw git history or PR list into Claude with a prompt that enforces structure, tone, and audience, then publish the output automatically as part of your release process.

This is different from a basic changelog generator. A changelog is typically a chronological, developer-facing log of changes. Release notes are a curated, audience-specific summary tied to a version — they group related commits into features, explain why something matters, flag breaking changes clearly, and often need a different tone depending on whether they're going to end users, admins, or other developers. Below is a practical build you can ship today.

What the generator actually needs to do

A good release notes generator has four jobs:

  1. Collect the raw material — commits, merged PR titles/descriptions, or issue tickets since the last tag.
  2. Classify and group changes into categories (new features, improvements, fixes, breaking changes, deprecations).
  3. Rewrite technical commit language into plain, audience-appropriate prose.
  4. Format and publish the result — markdown for your docs, HTML for an in-app changelog widget, or a Slack message for internal stakeholders.

Claude is well suited to steps 2 and 3 because it can reliably follow a structural template and adjust tone on request, without you writing a custom NLP pipeline.

Step 1: Gather the diff since the last release

Pull everything merged since the last tag:

git log v1.4.0..HEAD --pretty=format:"- %s (%an)" > changes.txt

If you use GitHub, pulling merged PR titles and bodies via the API gives you richer input than commit subjects alone — PR descriptions usually explain the "why," which is exactly what you want Claude to lean on.

Step 2: Design a prompt that enforces structure

The biggest failure mode with LLM-generated release notes is inconsistency — one release reads like marketing copy, the next reads like a terminal log. Lock the format down in the system prompt.

You are a release notes writer for a developer tool.
Given a list of raw commit messages and PR titles, produce release notes in this exact structure:

## New Features
## Improvements
## Bug Fixes
## Breaking Changes (omit section if none)

Rules:
- Group related commits into a single bullet, don't list every commit verbatim.
- Write for a technical end user, not an internal contributor.
- No bullet should mention internal file names, ticket numbers, or author names.
- Breaking changes must include a one-line migration note.
- Keep each bullet under 25 words.

Step 3: Call the API

Here's a minimal integration using SubToAPI's HTTPS endpoint — useful if you want application API keys, usage tracking per team, and no separate Anthropic billing setup for this internal tool:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "system": "You are a release notes writer for a developer tool. Follow the fixed structure: New Features, Improvements, Bug Fixes, Breaking Changes.",
    "messages": [
      {"role": "user", "content": "Raw changes:\n- fix: null pointer on empty cart (jsmith)\n- feat: add bulk export to CSV\n- feat: webhook retries with exponential backoff\n- chore: bump deps"}
    ]
  }'

The request/response shape matches the standard Messages API, so if you've used Claude directly before, nothing here changes except the base URL and key format. Full request options are in the docs; a minimal end-to-end example is in the quickstart.

Step 4: Automate it in CI

Wire this into your release pipeline so notes are generated the moment you tag a version:

import { execSync } from "child_process";

async function generateReleaseNotes(rawChanges) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      system: "You are a release notes writer. Use sections: New Features, Improvements, Bug Fixes, Breaking Changes.",
      messages: [{ role: "user", content: `Raw changes:\n${rawChanges}` }]
    })
  });
  const data = await res.json();
  return data.content[0].text;
}

const diff = execSync("git log $(git describe --tags --abbrev=0)..HEAD --pretty=format:'- %s'").toString();
const notes = await generateReleaseNotes(diff);
console.log(notes);

Pipe the output straight into a markdown file in your docs repo, a GitHub release description, or a Slack webhook for internal visibility.

Step 5: Add a tone switch for different audiences

Since you're generating from the same raw input, it's cheap to produce two versions in one CI step: an internal, more technical version for your team's Slack channel, and a plain-language version for public release notes. Just swap the system prompt's audience line — "Write for internal engineers who know the codebase" vs. "Write for end users with no technical background" — and reuse everything else.

Step 6: Handle streaming for long release cycles

If you're summarizing a month of changes across dozens of PRs, the response can run long. Stream it so your CI logs or a live dashboard show progress instead of waiting on a single blocking call — see streaming for the event format. For releases involving multiple repos, you can also use tool use to have Claude call back into your PR API mid-generation to pull missing context, like a linked issue description, instead of relying only on what you pasted into the prompt.

Keep a human in the loop for breaking changes

Automate the draft, not the final sign-off. Breaking changes and migration notes especially deserve a quick human read before publishing — a misworded migration instruction costs your support team more time than writing the notes manually would have. Treat Claude's output as a strong first draft, not the final copy.

If you're running this as a scheduled job across multiple repos or teams, a Solo plan covers a single pipeline; move to Team if multiple people need their own keys and usage visibility in the same dashboard.

FAQ

Does this replace a changelog entirely? No — a changelog is a raw, chronological record; release notes are a curated summary built from that record. Many teams generate both: the changelog mechanically, the release notes with Claude for human readability.

How do I stop Claude from hallucinating features that weren't actually shipped? Only feed it real commit messages and PR descriptions as source material, and instruct it explicitly not to add detail beyond what's provided. Review before publishing, especially for breaking changes.

Can I generate release notes in multiple languages? Yes — add a target language instruction to the system prompt and run the same raw diff through once per language. No separate pipeline needed since the grouping logic stays identical.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →