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:

TypeMeaningCHANGELOG section
featA new feature visible to usersFeatures
fixA bug fixBug Fixes
docsDocumentation changes only
styleFormatting, whitespace — no logic change
refactorCode restructuring — no feature or fix
perfPerformance improvementPerformance
testAdding or updating tests
buildBuild system or dependency changes
ciCI configuration changes
choreMaintenance tasks
revertReverting 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

VagueSpecific
fix: Fix bugfix: Prevent null pointer when cart is empty at checkout
feat: Add featurefeat(auth): Add TOTP-based two-factor authentication
refactor: Refactor coderefactor(parser): Extract token validation into standalone module
chore: Update depschore: Upgrade PostgreSQL driver from 14.2 to 15.0
docs: Update readmedocs: 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

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 #X or Fixes #X — automatically closes the issue when the PR merges (GitHub/GitLab)
  • Refs #X or Related to #X — links without closing
  • Part 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-patternProblemBetter
WIPNo informationfeat(auth): Implement TOTP setup flow (in progress)
fix: fixedDuplicate and vaguefix(api): Return 404 instead of 500 for missing resources
changesNoiseDescribe the actual change
as per review commentsNo substanceList what specifically changed
updateAlmost meaninglessSpecify what was updated and why
minor fixRelative and vagueDescribe the specific fix
tempNever meant to be permanentAddress 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 and BREAKING 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.