Skip to main content
← All posts
4 min read

The Cost of a Bad Commit Message

Bad commit messages don't waste seconds. They waste hours, weeks later, when you're debugging an incident at 2am.

Share

It's 2:14am. Production is down. You're git-bisecting to find the regression. You land on commit a4f3c12. The message reads:

fix bug

That's it. No context. The diff is 200 lines across 8 files.

You've just lost 20 minutes you didn't have, because someone typed three words instead of a paragraph six months ago.

This is the actual cost of bad commit messages. Not the seconds saved typing them. The hours spent later trying to understand why something exists.

What commit messages are for

A commit message has one reader: a future engineer trying to understand why a change was made.

That engineer might be you, six months from now. Or a new hire reading git blame. Or the on-call engineer who needs to decide whether to revert the change.

What they need:

  • Why the change was made (not what — the diff shows that)
  • What broke if it's a fix (so they can verify the fix is still needed)
  • Why this approach if there were alternatives (so they don't undo the work)
  • Links to issues, RFCs, or discussions

What they don't need:

  • A paraphrase of the diff
  • "Updates code" / "small fix" / "WIP"
  • Co-author lines without context

The good commit message

Conventional Commits format with substance:

fix(billing): handle missing card in Stripe webhook

The webhook payload for `customer.subscription.updated` doesn't always
include `default_payment_method`. We were assuming it did and crashing
when a subscription was paused via the dashboard.

Switched to fetching the customer's default payment method from the
customer object as a fallback. This adds one Stripe API call per webhook
but webhook volume is low (~50/min) so the rate limit headroom is fine.

Fixes #2847.

Subject line: ≤50 chars, imperative mood, scope tag.

Body: explains the why. The diff shows the what. Mentions trade-offs (extra API call, but acceptable). Links to the issue.

This commit, six months later, answers "why does this exist?" in 30 seconds.

The bad commit message

fix bug

Six months later, this commit costs:

  • 15 minutes searching git history for context
  • 20 minutes reading the diff to reverse-engineer the intent
  • 30 minutes asking around to confirm the assumption
  • Possibly being wrong and reverting something that's load-bearing

You didn't save five minutes by typing "fix bug." You stole 60 minutes from your future self.

When the rule actually matters

For a small project nobody will read in 6 months — fine, type "fix bug" and move on.

For anything load-bearing (production code, libraries, anything with multiple maintainers) — this discipline pays back 100x.

The math: ~30 commits a week. Spending 2 extra minutes per commit = 1 hour/week. Saving 30 minutes per debug session, 4 sessions/month = 2 hours/month. After three months you're net positive forever.

The PR description ≠ commit message

A common excuse: "the PR description has the context."

The PR description disappears when you squash. Or it lives in GitHub forever, but git blame doesn't link to it. Or you migrate platforms and the PR is gone.

The commit message is portable. It's in the repo, in everyone's clone, forever. Put the context there.

If you squash on merge, configure your tooling to use the PR description as the commit message. Don't lose that work.

Templates that help

Add a commit template:

git config --global commit.template ~/.gitmessage
# .gitmessage
# <type>(<scope>): <subject> (≤50 chars)
#
# Why is this change needed?
#
# What is the user-visible behavior change?
#
# Notable trade-offs?
#
# Refs: #issue

Now git commit (without -m) opens an editor with this scaffolding. You'll write better messages by default.

What to do about it as a manager

You can't enforce good commit messages by yelling. You can:

  • Demo good ones in code review. "I love how Sarah explained the trade-off here. Try writing yours like this."
  • Add a CI lint for the format (commitlint, or similar). Subject ≤72 chars, scope present, body for non-trivial changes.
  • Use squash-merge with PR description → commit message conversion. Fix the PR description discipline; the commits inherit it.
  • Write your own well. People copy the senior engineer's style.

Don't enforce commit message length in CI as a hard gate. That produces compliance, not quality. People will write 100 chars of nothing to pass the lint.

The AI escape hatch

Most coding assistants now write commit messages. The output is okay but generic — they paraphrase the diff.

Use them as a draft, then add the context the AI doesn't have: why this approach over alternatives, what the trade-off is, what other code might need to change later. The AI got you 60% of the way; the human adds the last 40% that actually matters.

The takeaway

A commit message is a letter to a future debugger. Spend two minutes writing it well; save your future self an hour. The ROI is absurd. Make it a habit and your codebase becomes navigable instead of mysterious.

Work with me

I consult with engineering teams on AI adoption, cloud architecture, and engineering effectiveness. If this post surfaced a challenge you're facing, let's talk.

Get in touch →

Explore more on these topics:

Subscribe to new posts

Get an email when I publish something new. No spam, unsubscribe any time.