← Blog

why your commit messages are part of your product

most of us write commits just coz it's needed.

the ugly truth

most developers treat commits as checkpoints. write something, push, move on. especially in a QA phase — you'll see a chain of commits just saying 'bug fixes'. no context, no reason, nothing.

the problem is that every commit you write is a decision made permanent. and when something breaks in prod, or someone else has to pick up where you left off, or you need to roll back — that commit message is all you have.

the shift

earlier i was also the one who wrote basic commit messages but used to write what i actually changed - most of the time one liners. but hardly followed the commit standards.

actual commit messages from earlier days :
1. removed unused config variables

2. qa bug fixes

3. code review changes

actual commit messages from recent days :

1. feat: add AI summary generation via Groq API
- POST /api/summary - accepts raw text, returns structured summary
- Model configurable via GROQ_MODEL env var, defaults to llama3-8b-8192
- Prompt template loaded from prompts/summary.txt so it can be updated
  without a redeploy
- Rate limiting applied per user_id (10 req/min) via existing rate-limit
  middleware
- Groq client initialised once at startup (src/lib/groq.ts) and injected
  via context — avoids re-initialising per request
NOTE FOR DEPLOYMENT:
1. Add env variable: GROQ_API_KEY=<key from console.groq.com>
2. Add prompts/summary.txt to the deployment bundle — not in .gitignore
   but double-check before pushing if you customised it locally
2. feat(scripts): add Google Sheets URL support to import pipeline

- Controllers now accept either uploaded CSVs or a sheetUrl + gid param
  and build the export URL internally (resolveInputs)
- parseCSVFromUrl added to scripts/utils/csv.js — fetches via axios,
  reuses same validateHeaders as file uploads
- validate-members and sync-members both support file or URL input
- Fix date offset: pass raw:true to xlsx.read so DD/MM/YYYY cells
  aren't rewritten to Excel serials before parsing

hopefully you see the difference. the second set isn't just cleaner — it's actually useful.

when you're shipping fast, this matters more than people think:

  • anyone on the team can understand what changed without asking
  • that deployment note means no separate slack message, no quick call, no "hey what do i need to add?" — it's all right there, tied to the exact change
  • three months later when something breaks, you know exactly what that commit did and why

claude skills i created

if you also think the above helps, i've already created two claude code skills that write these commit messages for you — so you never fall back to lazy habits under pressure.

  • local-commit — writes a commit message for all staged + unstaged changes vs HEAD
  • staged-commit — writes a commit message for only what you've staged

copy the files below and drop them in your ~/.claude/commands/ folder. that's it — next time you're in claude code, just run /local-commit or /staged-commit.

~/.claude/commands/local-commit.md

---
description: "Write a commit message for all local changes (staged + unstaged, vs HEAD)"
allowed-tools: ["Bash(git diff HEAD:*)", "Bash(git diff HEAD --stat:*)", "Bash(git log --oneline -10:*)", "Bash(git status:*)"]
---

You are writing a commit message for **all local changes** — both staged and unstaged — compared to the last commit (HEAD).

## Step 1 — Read the diff

Run these in order:
1. `git diff HEAD --stat` — see which files changed and by how much
2. `git diff HEAD` — read the actual changes
3. `git log --oneline -5` — check recent commit style/tone

## Step 2 — Understand what changed

Group the changes by intent, not by file. Ask: what problem does this solve or what feature does this add?

## Step 3 — Write the commit message

Follow this format strictly:

<type>: <short summary in present tense, lowercase, no period>

- <bullet: what changed and why, broad not line-level>
- <bullet: another logical group of changes>
- <bullet: only if meaningfully different from above>

Types: feat · fix · refactor · docs · test · chore

Rules:
- First line: max ~72 chars, no file names, no "changed X in Y"
- Bullets: broad intent, not "updated line 42 in foo.ts"
- Group related changes into one bullet — don't write one bullet per file
- Do NOT run git commit — output the message only

~/.claude/commands/staged-commit.md

---
description: "Write a commit message for staged changes (git diff --cached)"
allowed-tools: ["Bash(git diff --cached:*)", "Bash(git diff --cached --stat:*)", "Bash(git log --oneline -10:*)", "Bash(git status:*)"]
---

You are writing a commit message for the **staged** changes only.

## Step 1 — Read the diff

Run these in order:
1. `git diff --cached --stat` — see which files changed and by how much
2. `git diff --cached` — read the actual changes
3. `git log --oneline -5` — check recent commit style/tone

## Step 2 — Understand what changed

Group the changes by intent, not by file. Ask: what problem does this solve or what feature does this add?

## Step 3 — Write the commit message

Follow this format strictly:

<type>: <short summary in present tense, lowercase, no period>

- <bullet: what changed and why, broad not line-level>
- <bullet: another logical group of changes>
- <bullet: only if meaningfully different from above>

Types: feat · fix · refactor · docs · test · chore

Rules:
- First line: max ~72 chars, no file names, no "changed X in Y"
- Bullets: broad intent, not "updated line 42 in foo.ts"
- Group related changes into one bullet — don't write one bullet per file
- Do NOT run git commit — output the message only

what i have realized

commits are not internal notes. they're part of how you ship.

every time you write a vague commit, you're creating future friction — for your teammate, for your future self, for whoever deploys next. and when you're building something solo, that person is always you.

good commit discipline also changes how you think before you push. you're forced to articulate what you actually changed and why. that pause catches more bugs than any linter.

a bad commit says "i was just trying to make it work."
a good commit says "i knew what i was doing, and here's the proof."

one more thing

there's a direct connection between how well you write commits and how well your users understand what you shipped.

i'm building commitposts.com — a tool that takes your merged PRs and commits, uses AI to turn them into clean user-facing changelog entries, and publishes them to a public changelog page, subscriber emails, and an in-app notification widget.

the better your commit messages, the better the output. vague commits produce vague changelogs. but if you're already writing detailed commits like the ones above, the AI has something real to work with.

good commits → good changelog → users who actually know what you shipped.