Every developer has seen it. A PR with a single commit message: "fixed stuff". Or "update". Or the absolute classic — "."
We laugh because we've all done it. But bad commit messages aren't just a matter of style — they cost real time every single day:
- Code reviews take longer because there's no context
- Bisecting bugs becomes guesswork
- Changelog generation turns into manual archaeology
- New team members can't understand the project's history
I've reviewed thousands of PRs and written more commits than I care to count. Here's what actually works — practical rules you can start using today.
The 7 Rules of Good Commit Messages
These aren't theoretical. Every single one solves a real pain point.
1. Separate subject from body with a blank line
Bad:
fix the login bug and update the README while I was at it
Good:
fix(auth): handle token expiration on 401 responses
The login flow silently fails when a cached token expires mid-session.
Now the interceptor catches 401 errors and triggers the refresh flow
before the user notices anything wrong.
Fixes #342
Git can handle multi-line messages (git commit -m "subject" -m "body"). Use that power.
2. Limit the subject line to 50 characters
Your commit subject shows up everywhere: git log --oneline, GitHub PRs, Slack notifications, CI dashboards. Keep it scannable.
3. Capitalize the subject line
"add user routes" vs "Add user routes". Capitalize. It's a sentence.
4. Do not end the subject with a period
50 characters is already tight. Don't waste one on a period.
5. Use the imperative mood in the subject line
Think "This commit will...":
- ✅ "Fix memory leak in cache layer"
- ❌ "Fixed memory leak in cache layer"
- ❌ "Fixes memory leak in cache layer"
The imperative matches Git's own convention (Merge branch 'main') and makes every commit read like an instruction, not a diary entry.
6. Wrap the body at 72 characters
Terminals are 80 columns wide. With 4-8 characters of indentation from git log, you have about 72 characters per line. Wrap manually for readability.
7. Use the body to explain what and why, not how
The code already shows how. Your message should answer:
- What was the context? (bug report, feature request, refactor trigger)
- Why was this approach chosen? (performance tradeoffs, alternatives considered)
- What side effects should reviewers watch for?
Conventional Commits: Because Consistency > Creativity
If you want your team to write good messages consistently, adopt the Conventional Commits spec. It's simple:
<type>(<scope>): <subject>
<body>
<footer>
Types that cover 90% of your commits:
| Type | When to use | Example |
|---|---|---|
feat |
New feature | feat(api): add user registration endpoint |
fix |
Bug fix | fix(cache): invalidate token on expiry |
docs |
Documentation | docs(readme): add setup instructions |
refactor |
Code change with no behavior change | refactor(db): extract query builder |
test |
Adding or fixing tests | test(auth): add edge case for empty tokens |
chore |
Maintenance tasks | chore(deps): update lodash to 4.17.21 |
The beauty of Conventional Commits: they're parseable. Tools like standard-version, semantic-release, and GitHub Actions can auto-generate changelogs, bump versions, and publish releases based on your commit history.
Building the Habit Without the Pain
Knowing the rules and following them every time are two different things. Here's what actually works:
1. Use a prepare-commit-msg hook
Drop this into .git/hooks/prepare-commit-msg:
#!/bin/bash
echo ""
echo "# Please enter your commit message. Headers look like:"
echo "# feat(scope): add user authentication"
echo "# fix(scope): handle null pointer in parser"
echo "# docs(readme): update API examples"
echo "# refactor(db): extract query builder utility"
echo "# test(scope): add regression test for issue #123"
echo "# chore(deps): update dependency versions"
It's just a reminder — but reminders work.
2. Set up a commit template
git config --global commit.template ~/.git-commit-template.txt
Write a template file:
# <type>(<scope>): <subject>
# |<---- 50 chars max ---->|
#
# <body>
# |<---- 72 chars max ---->|
#
# Why is this change needed?
# What approach was taken?
# Any side effects or risks?
3. Automate it
I got tired of typing commit messages manually, so I built git-copilot — a tiny CLI tool that reads your staged changes and generates a conventional commit message instantly.
$ git add .
$ git-copilot gen
✨ feat(api): add user routes and controller
No AI, no API keys, no internet. It uses smart pattern matching to detect the type, scope, and message from your changes. Free and open-source (MIT):
For teams that want multi-line body templates, CI/CD output formats, and breaking change tracking, there's a Pro Templates Pack on Gumroad ($9.99).
The Bottom Line
Good commit messages aren't pedantry — they're a force multiplier. A team that writes clear, consistent commits:
- Reviews PRs 30-40% faster
- Ships releases with auto-generated changelogs
- Spends less time digging through
git blame - Onboards new members in hours instead of days
Start with one rule today. Add another next week. By next month, "fixed stuff" will be a distant memory.
What's your team's commit message convention? Drop it in the comments — I'm always looking for new approaches.
Top comments (0)