Writing conventional commits with AI: a 60-second workflow
Conventional Commits explained, plus a pre-commit hook that drafts the message from your staged diff. Saves you 30 seconds, 50 times a day.
Writing conventional commits with AI: a 60-second workflow
I spend more time writing commit messages than I'd like to admit. Not because commit messages are hard — because they're tedious. Conventional Commits give them structure; AI fills in the prose. Together you spend two seconds, get a useful message, and move on.
This post explains the standard, then shows the pre-commit hook I actually use.
The standard
Conventional Commits 1.0.0 is a tiny spec. The grammar fits in one paragraph:
<type>[optional scope][!]: <description>
[optional body]
[optional footer(s)]
Required: type and description. Common types:
feat:a new featurefix:a bug fixdocs:documentation onlystyle:formatting, no logic changerefactor:neither feat nor fixperf:performance improvementtest:adding or fixing testsbuild:build system or dependenciesci:CI configurationchore:everything elserevert:reverts a previous commit
Scope is freeform (feat(api):, fix(auth):). A ! before the colon or a BREAKING CHANGE: footer marks a breaking change — this is what tools like semantic-release use to compute version bumps.
Examples from a recent week of mine:
feat(rate-limit): allow per-key TTL override
fix(jwt): reject tokens with future iat
docs(readme): clarify install on macOS arm64
refactor(handlers): collapse duplicate error mapping
perf(cache): switch hot path from sync.Map to atomic.Pointer
chore(deps): bump go-redis to v9.10.0
Why bother? Because every modern release-automation tool — semantic-release, release-please, changesets, GitVersion — reads these prefixes to compute versions and generate changelogs. Free CHANGELOG.md is worth the discipline.
The 60-second workflow
The friction point is "I just wrote a commit, now I have to figure out the right type and scope." That's a parsing problem. The model is good at it.
Here's a .git/hooks/prepare-commit-msg hook that drafts a conventional commit message from the staged diff:
#!/usr/bin/env bash
# .git/hooks/prepare-commit-msg
COMMIT_MSG_FILE="$1"
COMMIT_SOURCE="$2"
# Skip if user passed -m or this is a merge/amend
if [ -n "$COMMIT_SOURCE" ]; then
exit 0
fi
# Skip if there's already a non-empty message
if [ -s "$COMMIT_MSG_FILE" ] && ! grep -qE '^[[:space:]]*$' "$COMMIT_MSG_FILE"; then
exit 0
fi
DIFF=$(git diff --cached --stat --no-color; echo; git diff --cached --no-color | head -c 6000)
if [ -z "$DIFF" ]; then
exit 0
fi
PROMPT="Generate a Conventional Commits 1.0.0 message for this diff.
Format: <type>(<scope>): <description>
Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.
Use lowercase. No quotes. Max 72 chars subject. Add a body only if non-obvious.
DIFF:
$DIFF"
# Call your preferred model — example uses the Anthropic CLI
MSG=$(claude -p "$PROMPT" 2>/dev/null) || exit 0
# Prepend the AI draft, keep any existing comments
{ echo "$MSG"; echo; cat "$COMMIT_MSG_FILE"; } > "$COMMIT_MSG_FILE.tmp"
mv "$COMMIT_MSG_FILE.tmp" "$COMMIT_MSG_FILE"
Save, chmod +x, and now git commit (with no -m) opens your editor with a pre-filled message. Edit if it's wrong, save if it's right.
Two notes on this:
- Don't auto-commit. The hook drafts, you review. Models occasionally pick the wrong type (
featvsfixis a judgment call when refactoring a bug into a new abstraction). You're still the author of record. head -c 6000keeps token cost bounded. For huge diffs, the model sees the file stat plus the first 6KB of patch text. Good enough to identify scope and type. If the diff is genuinely too big to summarize, the commit is probably too big.
What if I don't want a local hook?
If you don't want to wire shell scripts (corporate machine, polyglot team, prefer browser), the AI Commit Message tool does the same job: paste git diff --cached, get a conventional-format message. Runs in your browser using an in-page model — your diff doesn't go to a third-party API. Useful if your repo has secrets that you'd rather not pipe through someone else's logs.
For commits where you genuinely don't understand what changed (rebase aftermath, merge resolution, refactor you took over from a colleague), the AI Code Explainer is the prior step — paste the diff, get plain-English narration, then commit.
A note on scope and discipline
The biggest gain from conventional commits isn't the automation. It's that the type prefix forces you to think: "is this a feat or a fix?" If you can't decide, your commit is doing two things and should be split. The standard nudges you toward atomic commits without you having to remember to.
I've shipped projects where the entire CHANGELOG.md was generated automatically from commit history and read like it was written by hand. That's the bar. The hook above gets me there with no friction.
Try it
- AI Commit Message — paste a diff, get a conventional commit, in-browser model
- AI Code Explainer — narrate any diff or function in plain English