The Complete Guide to Writing Better Commit Messages
Your commit history is a form of documentation. Six months from now, when someone runs git blame on a confusing line of code, the commit message is the first thing they will read. If that message says "fix stuff" or "WIP," they learn nothing. If it says "fix race condition in session refresh when token expires during request," they understand the intent immediately.
Good commit messages are not about being pedantic. They are about respecting your future self and your teammates' time. A well-written commit history turns git log into a readable changelog that explains not just what changed, but why.
Why Commit Messages Matter
There are three moments when commit messages become critical:
- Code review: Reviewers read the commit message before the diff. A good message frames the change and tells the reviewer what to look for.
- Debugging:
git bisectis only useful when commit messages describe what each change does. If every message is "update," bisect finds the commit but tells you nothing about it. - Release notes: If your project uses automated changelogs (and it should), commit messages become user-facing documentation. "feat: add dark mode toggle" generates a clear changelog entry. "changes" does not.
The Conventional Commits Format
Conventional Commits is a specification that adds structure to commit messages. It is used by Angular, Vue.js, Ionic, and thousands of other projects. The format is:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
Types:
feat— A new feature (maps to MINOR in semver)fix— A bug fix (maps to PATCH in semver)docs— Documentation changes onlystyle— Formatting, missing semicolons, etc. (not CSS)refactor— Code change that neither fixes a bug nor adds a featureperf— Performance improvementtest— Adding or fixing testschore— Build process, dependencies, toolingci— CI/CD configuration changes
Scope is optional and describes the area of the codebase: feat(auth): add OAuth2 login or fix(api): handle null response from payment provider.
The Imperative Mood
The single most important style rule for commit messages: use the imperative mood in the subject line. Write "Add feature" not "Added feature" or "Adds feature."
The imperative mood reads as a command or instruction. It matches what Git itself uses: when you merge, Git creates "Merge branch 'feature'," not "Merged branch." Your commits should follow the same pattern.
A useful trick: your commit message should complete the sentence "If applied, this commit will ___." "If applied, this commit will add OAuth2 login" works. "If applied, this commit will added OAuth2 login" does not.
Why Non-Native Speakers Struggle with Imperative Mood
The imperative mood is genuinely difficult for non-native English speakers because it looks identical to the base form of the verb. In many languages, the imperative has a distinct conjugation that marks it clearly. In English, "Add" (imperative) looks exactly like "Add" (infinitive) and "add" (present simple for I/you/we/they). This ambiguity means there is no visual signal that the imperative is being used.
Additionally, many languages default to the past tense for describing completed actions, which is what a commit represents. In German, you might naturally write "Funktion hinzugefuegt" (function added). The mental translation often keeps the past tense.
The fix is mechanical: before committing, re-read your message and check if it starts with a bare verb. "Add," "Fix," "Remove," "Update," "Refactor," "Handle." If it starts with "Added," "Fixed," "Removing," or a noun, rewrite it.
10 Before-and-After Examples
Commit Message Rules to Adopt
- Subject line under 72 characters. This is a hard limit for many Git tools.
git log --onelinetruncates at 72. - Capitalize the first word. "Fix bug" not "fix bug" (some teams prefer lowercase with Conventional Commits; pick one and be consistent).
- No period at the end of the subject. The subject line is a title, not a sentence.
- Blank line between subject and body. Git uses this to distinguish the summary from the description.
- Body explains "why," not "what." The diff shows what changed. The body should explain the reasoning, the trade-offs, and the alternatives considered.
- Reference issues. Add "Closes #123" or "Refs #456" in the footer to link commits to issue trackers.
Tools for Enforcing Commit Standards
Humans are inconsistent. The way to get consistent commit messages is to enforce them with tooling.
- commitlint: A Node.js tool that validates commit messages against the Conventional Commits spec. Run it as a Git hook.
- Husky: Manages Git hooks so you can run commitlint automatically on every commit.
- commitizen: An interactive CLI that walks you through writing a properly formatted commit message.
For teams where non-native English speakers frequently contribute, the bln-commit-lint GitHub Action adds language-aware checking on top of format validation. It catches common L1 transfer errors in commit messages (like past tense instead of imperative, or missing articles) and suggests corrections inline in the PR.
Enforce Commit Standards Automatically
Catch format violations and language issues in commit messages on every PR.
Commit Lint Action