Stop Writing Changelogs — Let Claude Do It

Release day. Someone on the team has to go through the last 30 commits, figure out which ones are user-facing, write them up in plain language, and update the changelog file. Nobody wants to do it. It takes 20 minutes, it's tedious, and half the entries end up too technical for users to understand anyway.

What if you could type one command and have it done in 15 seconds?

That's exactly what we built at Pentacode using a Claude custom command. One /changelog invocation and Claude reads the git history, finds the associated pull requests, extracts developer-written changelog notes (or generates them from diffs), classifies everything, and writes the formatted changelog entry. No human changelog writing required.

Here's how to build the same thing for your project.

What Are Claude Code Custom Commands?

If you're not familiar with Claude Code, it's Anthropic's CLI tool that gives Claude direct access to your terminal and codebase. Custom commands are reusable prompts you define as markdown files. You put them in a .claude/commands/ directory, and they become available as slash commands.

The structure is simple:

your-project/
  .claude/
    commands/
      changelog.md    # creates the /changelog command
      review.md       # creates the /review command

Each file is a markdown document with instructions for Claude. When you type /changelog in your Claude session, Claude reads the markdown file and follows the instructions.

Two features make this powerful for changelog generation:

  1. $ARGUMENTS — Whatever you type after the command gets injected into the prompt. So /changelog minor passes "minor" as $ARGUMENTS.
  2. Shell injection with !`command` — You can embed shell commands in the markdown. They execute before the prompt is sent, and the output replaces the placeholder. This lets you inject live git data into Claude's context.

The Command

Here's the full /changelog command. Drop this into .claude/commands/changelog.md and adapt it to your project.

It's long — but that's the point. The more specific your instructions, the better Claude's output. Every line here exists because we iterated on it until the output was consistently good.

Generate a changelog entry for the upcoming release.

## Instructions

### 1. Determine the new version number

Read `CHANGELOG.md` and find the latest version from the first `## X.Y.Z` heading.

- If `$ARGUMENTS` contains "minor": increment minor version, reset patch to 0
  (e.g., 1.45.7 -> 1.46.0)
- Otherwise: increment patch version (e.g., 1.45.7 -> 1.45.8)

### 2. Find new commits

Run `git log main..release --oneline` to get the list of commits on `release`
that are not yet on `main`. These are the changes to document.

If there are no commits, stop and inform the user that there are no new changes.

### 3. Analyze each commit and its pull request

For each commit:

1. **Find the associated pull request.** Use
   `gh pr list --search "<commit hash>" --state merged --json number,title,body --limit 1`
   to find the merged PR that contains the commit.

2. **Extract existing changelog lines from the PR body.** If the PR follows
   your template and has a `### Changelog` section with content, **use those
   lines as-is** — they were written by the developer and are the authoritative
   changelog text. Preserve the original section classification (feature vs bug
   fix vs internal).

3. **If no PR is found, or the PR has no changelog content**, fall back to
   reading the diff (`git show <hash>`) and the commit message to generate a
   changelog entry yourself.

4. **Classify each commit/PR as:**
   - **User-facing change** (new feature, UX improvement, bug fix) -> include
   - **Internal-only** (refactor, dependency bump, CI, tests) -> skip from
     changelog, but include in the internal summary (step 5)

### 4. Write the CHANGELOG.md entry

Prepend a new `## X.Y.Z` section at the top of `CHANGELOG.md` (after any intro
text). Follow these rules:

**Format:**

- Version heading: `## X.Y.Z` (no "v" prefix, no date)
- Section order (omit sections that have no entries):
  1. `### Changes & New Features`
  2. `### Bug Fixes`

**Writing style:**

- Each bullet is exactly **one concise sentence** ending with a period
- Keep bullets short and scannable; aim for one line (~120 chars) where possible
- Write from the **user's perspective**: describe what the user sees or can do,
  not what changed in the code
- Use **present tense** and **active voice**
- No technical jargon, no internal details, no code references
- Do NOT include: dependency bumps, refactors, test changes, CI/CD changes

**Bug fix style:**

- Start with a frequency qualifier when appropriate: "In rare cases...",
  "In some cases...", "Under certain circumstances..."
- When no qualifier fits, describe the broken behavior directly
- Focus on the symptom the user experienced, not the root cause

**Feature / improvement style:**

- Bold the feature name if it's a named, discoverable feature
- For UX improvements, describe the improved behavior

**Examples of good bullets:**

- "In rare cases, shifts with an open start time were started on the wrong day."
- "Hour targets can now be pasted using CTRL + V."
- "Vacation blocks incorrectly prevented submitting sick days."
- "The **Expiring Contracts** tile now also shows probation period end dates."

**Examples of bad bullets (too long, too technical, or multi-sentence):**

- "The schedule was reworked. Shifts can now be entered alongside absences."
  (two sentences)
- "A bug in the TimeEntry service was fixed that caused work hours to be
  calculated incorrectly." (technical reference)
- "A new API endpoint was added for retrieving cost centers."
  (developer perspective)

### 5. Present the result

After writing the changelog entry, show the user:

- The new version number
- The full changelog entry you wrote
- An **Internal Changes** section summarizing all internal-only changes
  (refactors, dependency bumps, CI changes, tests) as bullet points. This
  section is **output-only** — do NOT write it to the changelog file. It gives
  the team visibility into technical work that is not user-facing.
- A summary of how many commits were analyzed and how many were included vs
  skipped

Walking Through the Command

Let's break down what's actually happening here.

Version detection

Claude reads your existing CHANGELOG.md, finds the latest version number, and increments it. Pass minor as an argument for a minor bump, otherwise it patches. This means you never have to think about version numbers — Claude figures it out from the file.

Commit discovery

The command uses git log main..release to find commits that haven't been merged to main yet. This assumes a workflow where you merge features into a release branch before cutting a release. Adjust the branch names to match your workflow — develop..main, main..HEAD, whatever fits.

PR analysis

This is where it gets interesting. For each commit, Claude uses the GitHub CLI (gh) to find the associated pull request. If your PR template includes a changelog section, Claude extracts those developer-written notes and uses them as-is.

This is the key insight: the best changelog entries come from the developer who wrote the code, not from an AI guessing at the diff. Claude's job is to collect, classify, and format — not to invent descriptions from scratch.

When there's no PR or the changelog section is empty, Claude falls back to reading the diff and commit message. It still produces good output, but developer-written notes are better.

Writing style rules

The longest section of the command defines the writing style. This is deliberate. Without explicit constraints, Claude will write changelog entries that sound like a developer explaining what they changed in the code. Users don't care that you "refactored the TimeEntry service" — they care that "work hours are now calculated correctly."

The rules enforce:

  • One sentence per bullet — no run-on entries
  • User perspective — what changed for them, not what changed in the code
  • Present tense — reads naturally in a changelog
  • Frequency qualifiers for bugs — "In rare cases..." tells users how likely they were to hit this

Classification

Not every commit belongs in a changelog. Dependency bumps, refactors, CI changes, test additions — users don't need to know about these. Claude classifies each change and only includes user-facing ones in the file. Internal changes get listed separately in the output so your team still has visibility.

The PR Template Trick

The real power move is combining this command with a PR template. Add a changelog section to your .github/PULL_REQUEST_TEMPLATE.md:

### Changelog

#### Changes & New Features

-

#### Bug Fixes

-

#### Internal Changes

-

Developers fill this in when they open a PR. They know what they changed better than anyone. Then when /changelog runs, Claude picks up those notes automatically. The AI handles collection, classification, and formatting. The human provides the context.

This gives you the best of both worlds: developer-written changelog content with zero manual changelog file management.

Customizing It

The command above is a generalized version. Here's what you'll want to adapt:

Branch names — Change main..release to match your git workflow. If you deploy from main directly, you might use tags: git log v1.45.7..HEAD --oneline.

Language — The original command at Pentacode writes changelogs in German. Swap the writing style section for whatever language your users read.

Categories — Add or remove sections. Maybe you want a "Breaking Changes" section, or a "Beta Features" section. Add them to the format rules and classification criteria.

Tone — The writing style rules shape the output heavily. If you want a more casual tone, say so. If you want technical detail, relax the "no jargon" rule. Claude follows what you write.

Multiple changelogs — Pentacode maintains separate changelogs for their main app and their staff app. You can add logic to check which files were touched and write to different changelog files accordingly.

One Command, Zero Changelog Pain

This is a markdown file in a .claude/commands/ directory. That's it. No build step, no npm package, no CI pipeline to configure. You type /changelog, wait 15 seconds, and review the output.

The command is version-controlled alongside your code. Your whole team gets it. New team members get it on their first git clone. And when you want to improve the changelog format, you edit the markdown file — the same way you'd edit any other document.

Writing changelogs is one of those tasks that feels small but adds up. It's 30 minutes every release, it's context-switching from code to prose, and the output is often inconsistent. Let the AI handle it. You've got better things to build.