Stop Writing "Fixed Bug" as Your Commit Message

by Eric Hanson, Backend Developer at Clean Systems Consulting

The Problem With "Fixed Bug"

You're in the middle of an incident at 11pm. Something broke in production, you don't know when, and you need to find the commit that caused it. You run git log --oneline and you see this:

f3a1c2d fix
e72b931 Fixed bug
c14a882 update
b9f301e Fixed bug
a2c74f1 wip
991ba44 Fixed bug

Congratulations — you've just discovered that git log is useless on this project. You're going to have to read actual diffs to find the problem. That's going to take twenty minutes minimum, for a history that could have told you in two.

This is the consequence of "fixed bug" commit messages. Not the one time you wrote it in a hurry — the pattern, repeated across a team, across months, until the commit history becomes archaeological rubble.

Why It Happens

The "fixed bug" habit comes from treating commits as saves rather than documentation. When you're deep in a problem and finally crack it, the last thing you want to do is context-switch to writing prose about what you just did. You know what you fixed. You commit and move on.

The failure is not lack of care. It's lack of discipline around who the commit message is written for. It is not for you right now. It is for you in six months when you've forgotten the context. It is for your colleague who picks up the thread when you're on leave. It is for whoever runs git blame on this line in three years during an audit.

What a Commit Message Actually Needs

A commit message has two parts: the subject line (72 characters max, present tense, imperative mood) and an optional body separated by a blank line.

The subject line answers: what does this commit do?

The body answers: why was this necessary, and what context matters?

# Bad — says nothing
Fixed bug

# Slightly better — identifies what
Fix null pointer in OrderService

# Good — identifies what and why
Fix null pointer in OrderService for guest checkouts

Guest sessions don't always have an authenticated userId. The
getUser() call on line 47 assumed non-null and threw NPE when
a guest user initiated checkout. Added null check with fallback
to anonymous user context.

Fixes #1923.

That last example takes maybe ninety seconds to write. It will save someone hours someday — possibly you.

The Imperative Mood Rule

Git itself uses imperative mood in its auto-generated messages: "Merge pull request", "Revert commit", "Initial commit". There's a good reason for this: a commit message should complete the sentence "If applied, this commit will..."

  • "Fix null pointer in OrderService" ✓ — If applied, this commit will fix null pointer in OrderService
  • "Fixed null pointer" ✗ — reads like a past-tense status report
  • "Fixing null pointer" ✗ — progressive tense, reads like a work-in-progress note
  • "Null pointer fix" ✗ — not a sentence

This rule matters more when commits appear in automated changelogs, release notes, or tooling that processes commit messages. Conventional Commits (a specification worth adopting) builds on this with typed prefixes:

feat: add OAuth2 support for partner API integrations
fix: correct rounding error in tax calculation for EU orders
perf: add index on order_items.created_at to fix slow reports
refactor: extract payment gateway logic into dedicated service
chore: update Spring Boot from 3.1 to 3.3
docs: document retry behavior in PaymentService

The type prefix makes automated changelog generation trivial and gives reviewers instant context about the nature of the change.

What "Bug" Doesn't Tell Anyone

The word "bug" is not specific. Every fix fixes a bug by definition. Compare:

# Useless — the word "bug" carries zero information
Fixed bug in payment service

# Useful — names the failure mode, the location, the trigger
Fix duplicate charge on payment retry when gateway times out

Payment gateway occasionally returns a timeout after processing
a charge successfully. Our retry logic was treating the timeout
as a failure and submitting a second charge. Added idempotency
key (order_id + attempt_number) to prevent duplicate charges.

Reproducer: PaymentServiceTest#duplicateChargeOnRetry
Fixes #2041.

The "bug" version means you have to open the diff, understand the code change, and infer what behavior it was fixing. The specific version gives you the entire picture in thirty seconds.

Enforcing This Without Being a Bureaucrat

You cannot enforce good commit messages through code review alone — by the time the PR is up, the history is written. The practical approaches:

Commit-msg hook — runs a validation script on every commit message before it's accepted. A simple regex check ensures messages aren't just one word and are over a minimum length:

#!/bin/sh
# .git/hooks/commit-msg
MSG=$(cat "$1")
MIN_LENGTH=10

if [ ${#MSG} -lt $MIN_LENGTH ]; then
  echo "Error: Commit message too short. Describe what and why."
  exit 1
fi

if echo "$MSG" | grep -qiE '^(fix|update|wip|temp|asdf|test)$'; then
  echo "Error: Commit message too vague. Be specific."
  exit 1
fi

Install this via a tool like Husky for Node.js projects or a pre-commit config for Python projects, so it's shared across the team rather than living in each developer's local .git/hooks.

Squash on merge — if your PR workflow squashes commits at merge time, a well-written PR title becomes the commit message. This shifts the discipline to PR description rather than individual commits. It works, but you lose granularity in the history.

Conventional Commits + commitlint — the commitlint tool enforces the Conventional Commits spec at the commit-msg hook level. Zero ambiguity, machine-readable output, automatic changelogs.

The one thing that does not work: periodic lectures in team meetings. The habit only changes when the feedback loop is immediate — which means automated tooling at commit time.

The Standard to Hold Yourself To

Before you push, read each commit message and ask: if I saw this message without any other context, would I know what changed and why? If the answer is no, git commit --amend or git rebase -i and fix it.

"Fixed bug" is a lie by omission. You know exactly what you fixed and why. The message just didn't say it.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

The Red Green Refactor Cycle Is Simpler Than Most TDD Articles Make It Look

Most TDD explanations spend so long on philosophy that the mechanics get lost. The red-green-refactor cycle is three steps that take two to five minutes per iteration. Here is what each step actually means in practice.

Read more

Shipping Imperfect Code on Time Beats Perfect Code That Never Ships

Software that exists and serves users imperfectly is categorically more valuable than software that would have been excellent but wasn't finished. The discipline is knowing what "imperfect" can safely mean in your context.

Read more

How to Transition from Employee to Independent Contractor

Quitting your job sounds exciting… until you realize you have to replace your salary. The shift isn’t just about freedom — it’s about learning how to operate like a business.

Read more

Stockholm Startups Can't Hire Backend Engineers Fast Enough — Here Is What Actually Works

You posted the backend role eight weeks ago. You've had twelve applicants, four interviews, and zero offers accepted. Meanwhile, the integration your sales team promised a client is still sitting in the backlog collecting dust.

Read more