Write release notes from commit messages
Summarise Northwind's commits into user-facing release notes — group by type, write for humans.
Published Jan 14, 2026
Commit messages are written for the next developer, not the customer. “Fix null check in invoice parser” means something to the engineer who wrote it and nothing to the Northwind account manager who has to explain the release. So the release notes either get skipped, or someone spends an hour every sprint translating shorthand into plain English.
This script hands that translation to Claude. You paste the sprint’s commit
messages into a Commits tab, run the script, and it groups them into
Features, Fixes, and Behind the scenes — rewritten for non-technical readers —
on a Release notes tab you can copy straight into an email or changelog.
What you’ll need
- A Google Sheet with two tabs. A
Commitstab holding one commit message per row in column A (paste them straight fromgit log --oneline), and aRelease notestab the script writes to — it creates that tab if it is missing. - An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - Nothing else — release notes land on their own tab.
The script
// The spreadsheet that holds the Commits tab and the Release notes tab.
const RELEASE_SHEET_ID = '1abcReleaseId';
// Tab names. The Commits tab is read; the Release notes tab is written.
const COMMITS_TAB = 'Commits';
const NOTES_TAB = 'Release notes';
/**
* Reads the Commits tab, asks Claude to rewrite the messages as
* user-facing release notes, and writes the result to the Release
* notes tab.
*/
function publishReleaseNotes() {
const ss = SpreadsheetApp.openById(RELEASE_SHEET_ID);
// 1. Read column A of the Commits tab and drop blank rows.
const commits = ss.getSheetByName(COMMITS_TAB)
.getRange('A2:A')
.getValues()
.flat()
.filter(Boolean);
if (!commits.length) {
Logger.log('No commits to summarise — nothing to do.');
return;
}
// 2. Turn the raw commits into grouped, plain-English notes.
const notes = writeReleaseNotes(commits);
// 3. Rebuild the Release notes tab so it always shows the latest run.
const sheet = ss.getSheetByName(NOTES_TAB) || ss.insertSheet(NOTES_TAB);
sheet.clear();
sheet.getRange(1, 1).setValue(notes);
Logger.log('Wrote release notes for ' + commits.length + ' commits.');
}
/**
* Asks Claude to group commit messages into Features, Fixes, and
* Behind the scenes, rewritten for non-technical readers.
*/
function writeReleaseNotes(commits) {
// A fixed set of headings keeps every release looking the same.
const prompt =
'Turn these git commits into user-facing Northwind release notes. ' +
'Group as: Features, Fixes, Behind the scenes. ' +
'Write for non-technical readers — no jargon, no commit hashes. ' +
'Skip purely internal commits if they would not interest a customer.\n\n' +
commits.join('\n');
return callClaude(prompt, 'claude-sonnet-4-6', 1500);
}
/**
* Minimal Anthropic API call. The key lives in Script Properties — it
* is never pasted into the code.
*/
function callClaude(prompt, model = 'claude-haiku-4-5-20251001', maxTokens = 400) {
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model,
max_tokens: maxTokens,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
publishReleaseNotesopens the release spreadsheet and reads column A of theCommitstab, flattening the range into a plain list and dropping blanks.- If there are no commits, it logs a message and stops — no wasted API call.
- It passes the list to
writeReleaseNotes, which builds a prompt pinning the output to three fixed headings: Features, Fixes, and Behind the scenes. - It calls Claude Sonnet, which is worth the extra cost here because rewriting terse commit shorthand into clear prose needs real reasoning.
- It rebuilds the
Release notestab from scratch and writes the result into cell A1, so the tab always reflects the latest run.
Example run
Say the Commits tab holds the raw output of git log --oneline for a sprint:
| A (commit message) |
|---|
| feat: add CSV export to invoice list |
| fix: null check in invoice parser |
| chore: bump dependency versions |
| feat: remember last-used currency per client |
| fix: timezone offset wrong on overdue badge |
After a run, cell A1 of Release notes holds something like:
Features
- You can now export your invoice list to a CSV file.
- Northwind remembers the last currency you used for each client.
Fixes
- Fixed a crash that could happen when opening certain invoices.
- The “overdue” badge now shows the correct date in every timezone.
Behind the scenes
- Updated internal libraries to keep things fast and secure.
That is text you can paste straight into a changelog or a customer email — no manual translation.
Run it
This is a once-per-sprint job, so run it by hand when the release is cut:
- Paste the sprint’s commit messages into column A of the
Commitstab. - In the Apps Script editor, select
publishReleaseNotesand click Run. - Approve the authorisation prompt the first time.
- Open the
Release notestab to read and copy the result.
To let non-editors trigger it, add a custom menu so it appears in the spreadsheet itself:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Release tools')
.addItem('Publish release notes', 'publishReleaseNotes')
.addToUi();
}
Watch out for
- Garbage in, garbage out. If your commit messages are vague (“fix stuff”,
“wip”), Claude has nothing to translate. Tidy commit messages — or a
conventional-commits prefix like
feat:orfix:— give far better notes. - The result is a draft, not a publish-ready post. Always read it before sending: Claude can mislabel an internal change as a feature, or vice versa.
- The whole commit list goes into one prompt. A very large sprint may exceed
the token budget — raise
max_tokens, or split the commits across two runs and merge the headings by hand. - Sensitive commit messages get sent to the API. If a message names an unannounced
client or an embargoed feature, strip it from the
Commitstab first.
Related
Generate and test email subject lines
A/B test AI-written Northwind subject lines for open rate — outputs ranked by past performance.
Updated Mar 3, 2026
Build retrieval-augmented Q&A over your data
Answer Northwind questions grounded in your own Sheet data — pass relevant rows as context.
Updated Feb 27, 2026
Build an AI weekly-report narrator
Turn Northwind metrics into a written executive summary — numbers in, prose out.
Updated Feb 23, 2026
Build a multi-step AI agent workflow
Chain Claude prompts to complete a Northwind task end to end — research → draft → critique → finalise.
Updated Feb 11, 2026
Adapt marketing copy per region
Localise Northwind tone and references by market with AI — same message, regional flavour.
Updated Jan 30, 2026