Multi-Audience Release Notes with Claude Code, Step by Step
Date: July 6, 2026 • Author: Second Axis
You shipped the release on Tuesday. It's Thursday and you're still writing about it: a customer-facing changelog entry, a Slack post so sales knows what to say, an internal summary for the all-hands, maybe API notes for the developers. PMs describe release communication as "often more time-consuming and messy" than the change itself, and the payoff for all that effort is the industry's worst-kept secret: "no one reads release notes".
Here's the thesis of this playbook: nobody reads release notes because everyone gets the same note. A bulleted list of merged PRs is simultaneously too technical for customers, too vague for sales, and too shallow for engineers. The fix is not better prose on one document. It's one release feeding five documents, each written for what its audience does next: a customer decides whether to try the feature, a CS rep decides which accounts to message, an engineer decides whether the API change affects them.
Can you build that with Claude Code? Yes, and this post is the complete build: the source-of-truth assembly, the per-audience writer agents, the critic that keeps internal details out of customer copy, and the archive that stops you from announcing the same feature twice.
What you're building
Every release, triggered by the release itself:
- Customer-facing release notes: benefit-first, in your product's voice, only the changes users can see.
- A sales/CS enablement note: what shipped, the talk track ("if a customer asks X, say Y"), and hints on which accounts to contact, drawn from the tickets and requests each change closes.
- An internal changelog: everything, including the flag flips, migrations, and fixes customers never see.
- A developer/API changelog, when the release touches the API: exact endpoints, deprecations, migration notes.
- In-app announcement copy: one or two sentences per headline feature, sized for a toast or a changelog widget.
All five come from a single assembled source of truth, all five pass a critic, and nothing external publishes without a human clicking approve.
Why the copy-paste-into-chatbot version fails
The naive version is pasting your merge log into a chat window and asking for release notes. It fails four ways, and each failure maps to a component you'll build:
- No source-of-truth join. A PR title like
fix: debounce webhook retries (#482)isn't a user-facing fact. Without joining PRs to tickets and tickets to the features users actually recognize, the model can only paraphrase engineering shorthand. This is the "narrative" complaint in PMs' own words: auto-generated notes where "the overall narrative... is not conveyed good enough". - No audience separation. One prompt produces one register. Customers get PR jargon or sales gets fluff; usually both.
- No memory of what was announced. A chat session doesn't know that you soft-announced the export feature two releases ago. Continuity is real, but it's something you engineer with committed state, not something a pasted log provides.
- No leak check. Feature flag names, incident references, and customer names in ticket titles flow straight into public copy unless something is explicitly checking for them.
The build below is those four fixes, in order.
Phase 0: Prerequisites
- Claude Code installed (
npm install -g @anthropic-ai/claude-code) and authenticated. Pro, Max, Team, or Enterprise if you want the cloud trigger and artifact publishing later. - GitHub access to the product repo(s):
ghCLI authenticated, so Claude Code can list merged PRs and diff against the last release tag. - Your tracker connected. Linear ships an official MCP server (
mcp.linear.app/mcp); Jira is covered by Atlassian's official MCP server. Either gives Claude the closed-tickets-since-last-release query plus the customer context attached to each ticket. - A useful head start: Anthropic's open-source knowledge-work-plugins include a Product Management plugin with commands like
/stakeholder-update. Good scaffolding for tone, but it doesn't contain the join, the five writers, or the release state below. That part is this playbook. - A dedicated git repo for the release-notes system. The archive design later depends on committed history.
Phase 1: The repo
release-comms/
├── CLAUDE.md # rules for every session
├── .mcp.json # Linear or Jira MCP (team-shared)
├── .claude/
│ ├── agents/
│ │ ├── customer-writer.md
│ │ ├── enablement-writer.md
│ │ ├── internal-writer.md
│ │ ├── api-writer.md
│ │ ├── inapp-writer.md
│ │ └── critic.md # judge loop before the human gate
│ └── skills/
│ └── release-notes/
│ └── SKILL.md # the orchestration recipe
├── style/
│ ├── customer.md # voice, banned words, structure
│ ├── enablement.md # talk-track format, objection handling
│ ├── internal.md
│ ├── api.md
│ └── inapp.md # length caps, CTA rules
├── mapping/
│ └── features.yml # repo/label/ticket-project → feature names
└── releases/ # committed: one folder per release
└── v2.14.0/
├── source.json # the assembled, enriched item list
├── customer.md ... inapp.md
└── state.json # what was announced, what was held
Two directories carry the design. style/ holds one style guide per audience, and these files are the direct fix for the narrative complaint: instead of hoping the model finds the right register, you version the register. The customer guide says "lead with what the user can now do, never name a flag, one screenshot placeholder per headline feature." The enablement guide says "every item gets a one-line talk track and a 'who to tell' hint." These are short files, 30-60 lines each, and arguing about them in a PR is exactly where your team's voice gets encoded.
mapping/features.yml is the join table between engineering noise and user-meaningful features:
- feature: "Scheduled exports"
labels: ["exports", "scheduler"]
ticket_projects: ["EXP"]
paths: ["services/export/**"]
audience: ["customer", "enablement", "inapp"]
- feature: "Webhook reliability"
labels: ["webhooks"]
audience: ["api", "internal"]
Every merged PR and closed ticket gets bucketed under a feature via labels, ticket project, or touched paths. Unmapped items land in an "unmapped" bucket that the run flags for a human, which is how the mapping file grows instead of rotting.
releases/ is the archive, and it's committed on purpose: the next run reads it to learn what has already been announced.
Phase 2: Source assembly and enrichment
The /release-notes skill's first job is building source.json, one entry per shipped change. Claude Code does this with tools, not vibes:
- PRs:
gh pr list --state mergedfiltered to merges since the last release tag (the previous tag is read from the latestreleases/folder). - Tickets: query Linear or Jira via MCP for issues closed in the same window, linked to those PRs where links exist.
- Join: bucket everything through
mapping/features.yml. Output per item: feature name, plain-language summary, PR/ticket references, audience list.
Then the enrichment pass, which is what separates this from a formatted merge log. For each item, Claude pulls the "why it matters" evidence from the tracker: which customer requests or support tickets does this close, which accounts asked for it, how long was it open. Where that evidence exists, it goes into the item:
{
"feature": "Scheduled exports",
"summary": "Exports can now run on a daily or weekly schedule",
"evidence": {
"closes": ["EXP-142", "EXP-198"],
"requested_by_hint": "4 linked customer requests, 2 enterprise",
"age_days": 210
},
"audience": ["customer", "enablement", "inapp"]
}
That evidence block is the reason sales actually reads the enablement note. "We shipped scheduled exports" is ignorable; "we shipped the thing two of your enterprise accounts have been asking about for seven months, here are the ticket links" is a task. Note the hint stays a hint: account names live in the tracker links, and whether they appear even in the internal enablement note is a policy you set in the style guide.
Run this phase interactively the first time and read source.json line by line. Everything downstream inherits its quality.
Phase 3: The writers and the critic
Five writer subagents, one per audience, each a small file in .claude/agents/. They differ only in which style guide they load and which audience's items they receive. One in full:
---
name: customer-writer
description: Writes the customer-facing release notes
tools: Read
model: inherit
---
You write customer-facing release notes for ONE release.
Input: releases/<version>/source.json (items where audience
includes "customer") and style/customer.md. Follow the style
guide exactly; it wins every conflict with your instincts.
Rules:
- Every item you mention must exist in source.json. Add nothing.
- Never mention: feature flags, incident IDs, internal codenames,
customer or account names, ticket IDs.
- No forward-looking promises. "Coming soon" only if the item
is explicitly marked approved_preannounce in source.json.
- Read the previous release's customer.md in releases/. If a
feature was already announced, reference it as an improvement,
never as new.
The other four follow the pattern: enablement-writer produces the talk track and who-to-tell hints, internal-writer includes everything, api-writer runs only when items carry the api audience, inapp-writer obeys hard length caps.
Then the critic, which is the judge loop this workflow lives or dies on:
---
name: critic
description: Grades all five outputs before the human gate
tools: Read, Grep, Bash
---
Check every draft in releases/<version>/ against:
1. TRUTH: every claim maps to an item in source.json. Grep each
feature mention; anything unmatched is FAIL.
2. LEAKS: customer.md and inapp.md contain no flag names,
incident references, ticket IDs, or account names. Grep the
known-flags list and mapping file identifiers against them.
3. PROMISES: no "coming soon", "next release", or future tense
about unshipped work, unless approved_preannounce.
4. REPEATS: nothing announced as new that appears as new in any
prior releases/*/customer.md.
Return PASS, or FAIL per document with the exact line and fix.
The skill wires them together: assemble, enrich, fan the writers out in parallel (each subagent gets its own context, so five audiences don't compete for one window), run the critic, loop failures back to the offending writer, maximum two revision rounds. Then it stops. The last step of the skill is not "publish"; it's "present the five drafts and wait." A human approves anything external. The internal changelog can auto-commit; customer notes, in-app copy, and anything a customer might screenshot go out only after you've read them. This gate is not a temporary training wheel, it's a permanent part of the design.
Phase 4: The release trigger and state
This workflow is event-shaped, not calendar-shaped: it should run when a release happens, not every Monday. Two good wirings:
- Managed: cloud routines support GitHub event triggers, including releases. Create a routine ("on release: run /release-notes") via
/scheduleor claude.ai/code/routines; it clones the repo, loads.claude/and.mcp.json, runs in a cloud sandbox, and pushes its output on aclaude/-prefixed branch, which doubles as your approval gate if a human merges it. - Manual: cutting a release is already a human moment, so running
claudein the repo and invoking/release-notescosts you one command at a time you're already at the keyboard. Plenty of teams should start here and only automate the trigger once the outputs are trustworthy.
Either way, continuity comes from the archive. Each run's first step is reading the previous releases/*/ folders: state.json records what was announced, what was deliberately held back, and any approved pre-announcements. A fresh session inherits all of it by cloning the repo. That is the whole memory mechanism, and it's why "never re-announce" is an enforceable rule rather than a hope: the critic can grep history that's actually there.
Add a Stop hook in .claude/settings.json posting run status to Slack, so a failed run on release day is visible within minutes, not when a customer asks why the changelog is stale.
Phase 5: Sharing with the team
Each output has a home, and the skill's final (post-approval) step routes them:
- Customer notes go to wherever your changelog lives: committed to the docs repo, pasted into your changelog tool, or published to your site's pipeline.
- The enablement note goes to the sales/CS Slack channel. There's no official Slack MCP server; the community korotovsky/slack-mcp-server works, or a ten-line webhook script does the same job with less surface area.
- The internal changelog can be published as an artifact: Claude Code renders markdown as a hosted page on claude.ai, private by default, with a stable URL across republishes and org-scoped visibility on Team/Enterprise plans. Two caveats to plan around: artifact publishing works from the CLI and desktop app, not from every automated surface, and viewers need to sign in to claude.ai. For a fully automated cloud trigger, the artifact step may be the piece that stays manual, or you swap it for a committed page in your internal docs.
- In-app copy gets pasted into your announcement tool by whoever approved it; it's two sentences, and the approval and the paste are the same moment.
Phase 6: Maintenance (what you own now)
- Style-guide drift. Your product's voice evolves; the guides must follow, and every edit changes what customers read. Treat
style/like code: PR review, no direct pushes. - Mapping upkeep. New services, new labels, new ticket projects. The "unmapped items" flag in each run is your prompt; ten minutes per release keeps the join sharp, skipping it for a quarter makes the notes vague again.
- Gate discipline. The failure mode of every approval gate is rubber-stamping. The week you stop actually reading the customer draft before approving is the week a flag name ships. Keep the diff small enough to read; that's what the critic is for.
- Archive hygiene.
releases/is the system's memory. Retro-editing published notes without updatingstate.jsonbreaks the never-re-announce check silently.
What this costs
This is the cheap end of the series to run. Per release, assuming 20-40 merged PRs and a similar count of tickets: source assembly is a few thousand tokens of tool results; each of five writers reads an item list plus a style guide (2-5K tokens in) and writes a page or less; the critic reads all five drafts plus history greps. Call it a five-writer fan-out over small inputs, twice if the critic forces a revision round. That sits comfortably inside a Pro or Max subscription's included usage, even releasing weekly. /usage after the first few runs will confirm it.
The real costs are elsewhere: the setup (mapping file, five style guides, critic tuning, a few interactive releases before you trust it) and the standing editorial gate, a human reading five short documents every release. That second cost is a feature. The whole point is that a person with judgment spends ten minutes reviewing instead of three hours writing.
The four practical challenges
- Setup at scale: not token scale this time, but join scale. The mapping file and enrichment pass (Phases 1-2) are what turn three tools' worth of noise into one source of truth; without them you've built a fancier merge log.
- Sharing with the team: five outputs, five destinations (Phase 5), each with its own delivery quirk, and the artifact path has plan and surface caveats.
- Maintenance: style guides, the mapping file, gate discipline, archive hygiene (Phase 6). Small individually, fatal collectively if dropped.
- Cost: modest tokens per release; the spend is setup engineering and the recurring editorial review, which no invoice will ever show you.
The managed alternative
Release-communication tools like LaunchNotes and Beamer productize the publishing half of this: a polished changelog surface, subscriber delivery, and in-app widgets without you building any of it. That's a real trade worth taking if delivery is your bottleneck. What stays manual is everything this playbook automates: the source-of-truth join from PRs and tickets, the audience-specific narrative, and the enablement depth that tells sales which accounts to call. In those tools, the words are still yours to write, five times, every release.
Doing this with Second Axis
Everything above is the do-it-yourself path. On Second Axis, multi-audience release notes is a workflow in the marketplace: connect GitHub, Jira or Linear, and Slack, choose the workflow, and set the goal ("per release: customer notes, sales enablement, internal changelog, in our voice, gated on my approval"). It runs on each release, remembers what was already announced, and monitors itself: failed runs and source mismatches get found and alerted instead of surfacing as a stale changelog two releases later.