How to Write Better Git Commit Messages (Complete 2026 Guide)
A practical guide to writing meaningful git commit messages that your teammates and future self will thank you for. Includes Conventional Commits, real examples, and automation tips.
Your git history is a form of documentation. Every commit you write is a note to your future self — and to every developer who touches this codebase after you. Most developers treat commit messages as an afterthought, something to type quickly before moving on. That's a mistake that compounds over time.
Six months after writing a fix, you'll run git blame on a weird conditional and find your own commit message: "misc fixes". You'll have no idea what you changed, why you changed it, or whether it's safe to modify. This guide will help you never be in that situation again.
Why Commit Messages Matter More Than You Think
The Hidden Cost of Bad Commits
Bad commit messages don't just look unprofessional — they actively slow down development. Consider these real scenarios:
- Code review: A reviewer sees a 200-line diff with the message "update". They have to read every line to understand what changed and why. A good message would have cut that time in half.
- Debugging: A bug was introduced last month. You run git bisect to find the commit. Every commit message says "wip" or "fix". Binary searching through meaningless messages is its own nightmare.
- Changelog: It's release time. You need to write release notes. You pull up the last 50 commits and they all say "update", "fix", "changes". You spend two hours manually reading diffs.
- Onboarding: A new developer joins the team. They try to understand the codebase's history. The git log reads like abstract poetry.
How Poor Git History Slows Teams Down
At an individual level, bad commits are annoying. At a team level, they're expensive. Research consistently shows that developers spend 20-30% of their time understanding existing code — and git history is one of the primary tools for that understanding. Degrade the quality of your git history and you degrade the entire team's velocity.
The Anatomy of a Perfect Commit Message
A commit message has three parts: subject, body, and footer. Most commits only need a subject. Significant changes benefit from a body.
The Subject Line: 50 Characters, Imperative Mood
The subject line is the commit message's headline. It appears in git log --oneline, in GitHub's commit list, and in email notifications. Two rules that matter:
- Keep it under 50 characters. GitHub truncates at 72; keeping it under 50 gives breathing room for display contexts.
- Use imperative mood. Write "Add feature" not "Added feature" or "Adding feature". Think: "If applied, this commit will Add feature."
# Bad — past tense, vague
added some fixes to the authentication flow
# Bad — present progressive
fixing login bug
# Good — imperative, specific
Fix token expiration not cleared on logoutThe Body: Explain Why, Not What
The diff already shows what changed. The commit body should explain why. This is where you provide context that helps future developers understand the reasoning behind the change.
Fix token expiration not cleared on logout
Previously, logging out only cleared the session cookie but left
the cached auth token in localStorage. On subsequent login attempts,
the stale token was used before expiry check, causing 401 errors
until the token naturally expired (up to 24 hours).
Now clears both session cookie and cached token on logout.
Fixes: #234The Footer: Reference Issues and Breaking Changes
Use the footer to reference related issues, PRs, or to flag breaking changes:
BREAKING CHANGE: auth token storage moved from localStorage to httpOnly cookie
Fixes: #234
See also: #198Conventional Commits: The Standard Your Team Should Adopt
Conventional Commits is a specification that adds meaning to commit messages by using a structured format. It's become the de facto standard for open source projects and is adopted by teams at companies like Google, Microsoft, and thousands of startups.
The Conventional Commits Format
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Where type is one of:
- feat — A new feature (correlates with MINOR in Semantic Versioning)
- fix — A bug fix (correlates with PATCH in Semantic Versioning)
- docs — Documentation changes only
- style — Formatting, missing semicolons, whitespace (no logic changes)
- refactor — Code restructure without fixing a bug or adding a feature
- test — Adding or correcting tests
- chore — Build process, dependency updates, auxiliary tool changes
- perf — Performance improvements
- ci — CI/CD configuration changes
- build — Build system or external dependency changes
Real Examples from Open Source Projects
# React
feat(concurrent): add startTransition API
# Next.js
fix(router): resolve infinite loop on hash change
# TypeScript
perf(checker): cache symbol resolution results
# Vercel
chore(deps): upgrade turbopack to 1.12.4
# Your project
feat(auth): add Google OAuth login with refresh token rotation
fix(api): handle null response from /users endpoint gracefully
docs(readme): update Node.js version requirement to 18+Common Mistakes Developers Make
The "WIP" Problem
"WIP" commits happen when you need to save progress but aren't done yet. The fix: use git stash instead of committing work in progress. If you must commit, use a draft PR and squash before merging. "WIP" in a main branch commit history is always a mistake.
Over-Generic Messages
"Update", "Fix", "Changes" — these messages tell the reader nothing. Every commit is an update or a fix. Be specific about what was updated or what was fixed:
# Instead of:
Update styles
# Write:
style(nav): increase mobile menu touch target to 44px
# Instead of:
Fix bug
# Write:
fix(checkout): prevent duplicate order submission on double-clickMixing Multiple Changes in One Commit
A commit should represent one logical change. Mixing bug fixes with new features, or refactoring with behavior changes, makes it impossible to revert specific changes or understand the history.
How to Automate Perfect Commit Messages
Using AI to Generate Commits
The challenge with conventional commits is discipline. When you're in flow state and just want to push, stopping to craft a well-structured commit message breaks concentration. AI commit generators solve this by reading your actual diff and generating the message for you.
The key is that good AI commit generators don't just produce generic messages — they analyze what actually changed in your code and generate specific, accurate descriptions. The difference between "update auth" and "fix(auth): clear refresh token on logout to prevent 401 errors" is entirely about understanding the diff.
CommitCraft AI: One-Click Commit Generation in VS Code
CommitCraft AI is a VS Code extension that reads your staged diff and generates a conventional commit message in one click. You stage your changes, click the button in Source Control, and a structured message appears. You review it, edit if needed, and commit.
Stop writing commits manually
CommitCraft AI generates them from your diff in one click. Free tier available.
Install CommitCraft AI — FreeGetting Your Team on Board
Convincing a team to change commit habits is harder than changing your own. A few approaches that work:
- Start with the "why" — show a concrete example from your own codebase where a bad commit cost time
- Add a commitlint hook (npm install --save-dev @commitlint/cli @commitlint/config-conventional) to enforce the format automatically
- Don't enforce immediately — suggest conventional commits for new features, gradually raising the bar
- Share tools like CommitCraft AI that make compliance effortless rather than a chore
The goal isn't perfect adherence to a spec — it's a git history that communicates clearly. Whether you use Conventional Commits exactly or a similar structured format matters less than the principle: every commit should explain what changed and why.
Try CommitCraft AI Free
Generate conventional commits, PR descriptions & changelogs from your diff in one click. 5 free generations/month.
Install for VS Code — Free