Writing Effective Commit Messages: Advanced English and Conventional Commits
Go beyond basic commit messages — learn Conventional Commits format, semantic meaning, linking issues, and the English writing patterns that make git history truly useful.
A commit message is a letter to the future. Six months from now, a colleague — or you — will run git log trying to understand why a specific change was made. The quality of your commit messages determines how much time that investigation takes.
For non-native English speakers, commit messages present a specific challenge: they need to be concise (ideally under 72 characters in the subject line), semantically precise, and written in a specific grammatical style that does not match everyday speech.
This guide covers the advanced patterns: the Conventional Commits specification, the semantic vocabulary of changes, linking issues, and the English writing discipline that separates a good git history from an exceptional one.
The Grammar Rule That Surprises Everyone
The universal convention in English commit messages is to write the imperative mood — a verb form that sounds like a command:
- Correct:
Fix the race condition in the session handler - Incorrect:
Fixed the race condition(past tense) - Incorrect:
Fixing the race condition(gerund) - Incorrect:
This fixes the race condition(declarative)
Why the imperative? Because git revert generates messages like Revert "Add feature X". The subject line of a commit should complete the sentence: “If applied, this commit will…”
- “If applied, this commit will Fix the race condition in the session handler.”
- “If applied, this commit will Add support for OAuth 2.0 device flow.”
This is the grammatical contract of the commit message format.
The Conventional Commits Specification
Conventional Commits is a widely adopted specification that adds structured semantic meaning to commit messages. It is the foundation for automated changelogs, semantic versioning, and CI/CD pipelines that parse commit history.
Basic Format
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
The Types and Their Meanings
Each type carries a precise semantic meaning:
| Type | Meaning | CHANGELOG section |
|---|---|---|
feat | A new feature visible to users | Features |
fix | A bug fix | Bug Fixes |
docs | Documentation changes only | — |
style | Formatting, whitespace — no logic change | — |
refactor | Code restructuring — no feature or fix | — |
perf | Performance improvement | Performance |
test | Adding or updating tests | — |
build | Build system or dependency changes | — |
ci | CI configuration changes | — |
chore | Maintenance tasks | — |
revert | Reverting a previous commit | — |
The Breaking Change Marker
A ! after the type (or BREAKING CHANGE: in the footer) signals that this commit introduces an incompatible API change — it bumps the major version in semantic versioning:
feat!: Remove deprecated v1 authentication endpoints
BREAKING CHANGE: The /api/v1/auth/* endpoints have been removed.
Consumers must migrate to /api/v2/auth/* before upgrading.
See migration guide: https://docs.example.com/migration/v1-to-v2
Writing the Subject Line
The subject line is the most-read part of a commit message. Rules:
- 50-72 characters maximum (72 is the hard limit most tools enforce)
- Lowercase after the type prefix (except proper nouns)
- No period at the end
- Be specific — “Fix bug” is useless; “Fix null pointer in cart item removal” is useful
Specificity Examples
| Vague | Specific |
|---|---|
fix: Fix bug | fix: Prevent null pointer when cart is empty at checkout |
feat: Add feature | feat(auth): Add TOTP-based two-factor authentication |
refactor: Refactor code | refactor(parser): Extract token validation into standalone module |
chore: Update deps | chore: Upgrade PostgreSQL driver from 14.2 to 15.0 |
docs: Update readme | docs: Add environment variable reference to deployment guide |
Using Scope
The optional scope in parentheses identifies the subsystem or module affected:
feat(api): Add pagination to /users endpoint
fix(auth): Correct token expiry calculation for UTC offsets
perf(search): Cache Elasticsearch query results for 60 seconds
Scopes are team conventions — agree on a consistent list (e.g., api, auth, ui, db, ci) and stick to it.
Writing the Commit Body
The body is optional for simple changes but essential for:
- Non-obvious decisions
- Bug fixes where the root cause is not self-evident
- Breaking changes
- Anything that will confuse the next reader
Body Writing Rules
- Separate from the subject with a blank line
- Wrap at 72 characters per line (most editors can do this automatically)
- Explain why, not what — the diff shows what changed; the body explains the reasoning
- Use full sentences with capital letters and punctuation
- Write in present tense for descriptions, but past tense for context is acceptable
Body Examples
Good body — explains the reasoning:
fix(cache): Use write-through strategy for session data
Previously, sessions were cached with a write-back strategy, which
created a window where a server restart could lose session data
before it was persisted. This was acceptable in development but
caused intermittent logouts in production during rolling deploys.
Write-through adds a small latency cost (~2ms per write) but
eliminates the data loss risk entirely.
Good body — explains a constraint:
feat(export): Limit CSV export to 100,000 rows
The previous implementation had no row limit, which allowed users
to trigger exports that consumed all available memory on the export
worker. We've added a hard limit of 100,000 rows with a clear
error message directing users to the API for larger datasets.
This is a temporary measure while we implement streaming exports
in the next sprint. See JIRA-4821.
Linking Issues and References
Issue References in the Footer
The footer section is used for metadata — issue links, co-authors, and breaking change notes:
Closes #342
Fixes JIRA-1234
Refs #298, #301
Reviewed-by: Alice Chen <alice@example.com>
Co-authored-by: Bob Smith <bob@example.com>
Keyword conventions:
Closes #XorFixes #X— automatically closes the issue when the PR merges (GitHub/GitLab)Refs #XorRelated to #X— links without closingPart of #X— signals this is one commit in a larger feature
Multi-Repository References
When a commit addresses an issue in a different repository:
Fixes org/other-repo#45
Linking to Documentation and Decisions
See ADR-027 for the rationale behind this approach.
Migration guide: https://docs.example.com/migrate/v3
Spec: https://www.rfc-editor.org/rfc/rfc7519 (JWT)
The Revert Commit
When reverting, Git generates:
Revert "feat(auth): Add TOTP-based two-factor authentication"
Add a body explaining why you reverted:
Revert "feat(auth): Add TOTP-based two-factor authentication"
Reverting due to a critical regression in the SMS fallback path.
When TOTP is enabled and the user has no recovery codes, the login
flow enters an unrecoverable state. Reverting to unblock release;
the fix will be tracked in AUTH-892.
Commit Message Anti-Patterns to Avoid
These are common patterns — some from non-native speakers, some universal:
| Anti-pattern | Problem | Better |
|---|---|---|
WIP | No information | feat(auth): Implement TOTP setup flow (in progress) |
fix: fixed | Duplicate and vague | fix(api): Return 404 instead of 500 for missing resources |
changes | Noise | Describe the actual change |
as per review comments | No substance | List what specifically changed |
update | Almost meaningless | Specify what was updated and why |
minor fix | Relative and vague | Describe the specific fix |
temp | Never meant to be permanent | Address and commit properly |
Practical English Tips
Use the active voice. Commit messages should be active, direct, and precise:
- Active:
Remove deprecated authentication endpoints - Passive:
Deprecated authentication endpoints are removed
Prefer concrete verbs. Instead of “update” or “change”, use the verb that precisely describes the change:
Add,Remove,Replace,Extract,Rename,Move,Fix,Correct,Prevent,Allow,Disable,Enable,Upgrade,Downgrade,Revert
One change per commit. This is as much an English principle as a git principle. If your commit message needs “and” to describe what changed, consider splitting it into two commits.
Key Takeaways
- Use the imperative mood: “Fix the bug”, not “Fixed” or “Fixes”.
- Conventional Commits adds structured semantic meaning:
feat,fix,refactor,chore, and others signal impact level and CHANGELOG category. - The
!suffix andBREAKING CHANGE:footer signal major version bumps. - Subject lines should be 50-72 characters, specific, and use concrete verbs.
- Body explains the why, not the what — the diff shows what changed.
- Footers contain issue references (
Closes #X), co-authors, and breaking change notes. - Avoid anti-patterns:
WIP,fix: fixed,update,changes— they corrupt your git history.
A well-maintained git history is a living document. Write it for the engineer who will need it at 11 PM during an incident — that engineer might be you.