How to Build a Claude-Powered Changelog Generator
Building a Claude-powered changelog generator means feeding your git history, commit messages, or merged pull requests into Claude and having it produce a clean, categorized changelog your users can actually read. Instead of manually rewriting "fix: null check in auth.ts" into "Fixed an issue where users could get logged out unexpectedly," you automate that translation with a prompt and an API call.
This is a solved problem once you understand the three pieces involved: data extraction (pulling the raw commits or PR titles), structured generation (a prompt that forces Claude into a consistent format), and automation (wiring it into your CI/CD so it runs on every release). Below is a practical walkthrough you can adapt to your own release process.
Why use Claude instead of a template script
Template-based changelog tools (conventional-commits parsers, auto-changelog, etc.) work fine when every commit message is perfectly formatted. In practice, most repos have a mix of terse commits, vague messages, and squashed PRs with no structure at all. Claude handles the messy middle ground: it can infer intent from a commit diff or title, group related changes, filter out noise like "wip" or "typo fix," and write in a consistent voice — something regex-based tools can't do.
Step 1: Collect the raw input
Start with whatever source of truth you already have. The simplest is git log between two tags:
git log v1.4.0..v1.5.0 --pretty=format:"%h %s" --no-merges > commits.txt
If you squash-merge PRs, pulling PR titles and descriptions from your git provider's API gives richer context than commit subjects alone. Either works — Claude doesn't need perfect input, just enough signal.
Step 2: Design a prompt that forces structure
The quality of your changelog depends almost entirely on prompt structure, not model cleverness. Give Claude:
- The raw commit list or PR data
- A fixed output format (Added / Changed / Fixed / Removed)
- Explicit instructions to skip internal-only changes
- A tone instruction (user-facing, concise, no jargon)
You are generating a public changelog from internal git commits.
Rules:
- Group entries under: Added, Changed, Fixed, Removed
- Skip commits about tests, CI, linting, or internal refactors unless they affect behavior
- Rewrite each entry in plain English for end users, one line each
- Do not invent features that aren't in the commits
- Output valid Markdown only, no preamble
Commits:
{{commit_list}}
This "rules + raw data" pattern is the backbone of most generation pipelines — the same structure works for summarizers, resume screeners, or any tool that needs reliable formatting from unstructured input.
Step 3: Call the API
Once you have the prompt and data, the integration is a single API call. If you're already using Claude through SubToAPI, the request looks like this:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "You are generating a public changelog... Commits:\n'"$(cat commits.txt)"'"
}
]
}'
In Node.js, the same call inside a release script:
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",
max_tokens: 1024,
messages: [{ role: "user", content: prompt + commitList }],
}),
});
const data = await res.json();
fs.writeFileSync("CHANGELOG_DRAFT.md", data.content[0].text);
SubToAPI exposes Claude through a standard HTTPS endpoint with sub_live_ application keys, so you can plug this into a build script without managing separate per-model credentials or quotas for each teammate running the release process. See the quickstart and messages endpoint docs for the full request/response shape.
Step 4: Automate it in CI
Wire the script into your release pipeline so the changelog draft is generated automatically on tag creation:
- name: Generate changelog
run: |
git log ${{ steps.prev_tag.outputs.tag }}..HEAD --pretty=format:"%h %s" --no-merges > commits.txt
node generate-changelog.js
env:
SUBTOAPI_KEY: ${{ secrets.SUBTOAPI_KEY }}
Have the job open a PR with the generated CHANGELOG_DRAFT.md rather than committing directly — a human should still skim it before it goes out, since Claude can occasionally misgroup an edge-case commit.
Step 5: Handle scale and streaming for large releases
For releases with hundreds of commits, you'll hit a point where it's better to stream the response instead of waiting on a single blocking call, especially if you're generating the changelog as part of an interactive release dashboard rather than a CI batch job. The streaming docs cover how to consume partial output token-by-token if you build that kind of UI.
If your team runs multiple products with separate release cadences, it's worth putting changelog generation behind its own API key so you can track usage and costs per project independently rather than lumping it in with your main application traffic. A Solo plan on pricing is usually enough for a single repo's release cycle; teams running changelogs across several repos tend to move to the Team tier once more than one person needs dashboard access.
Keeping output consistent across releases
A common failure mode is inconsistent tone between releases — one changelog sounds formal, the next sounds casual. Fix this by pinning the prompt in version control alongside your release scripts, not in a dashboard or wiki. Treat the prompt file the same way you treat your CI config: reviewed, diffed, and tagged with the repo. Small wording changes (e.g., "Fixed" vs "Resolved") compound across dozens of releases and are easy to miss if the prompt lives outside your codebase.
questions
Does Claude need access to the actual code diff, or just commit messages? Commit messages or PR titles are usually enough for a readable changelog. Diffs help when commit messages are vague, but they increase token usage significantly — reserve full diffs for commits Claude can't interpret from the message alone.
How do I stop Claude from listing internal-only changes? Be explicit in the prompt about what to exclude (tests, CI, refactors, dependency bumps) and give a few examples of commits that should be skipped. Vague instructions like "only show important stuff" produce inconsistent results.
Can this run fully automatically without human review? Technically yes, but it's not recommended. Have the automation open a draft PR instead of publishing directly — Claude occasionally miscategorizes an edge case, and a 30-second review before publishing avoids shipping an inaccurate changelog to users.