DEV Community

z z
z z

Posted on

Stop Writing 'Fixed Stuff' — A Practical Guide to Commit Messages That Don't Suck

Every developer has seen it. A PR with a single commit message: "fixed stuff". Or "update". Or the absolute classic — "."

We laugh because we've all done it. But bad commit messages aren't just a matter of style — they cost real time every single day:

  • Code reviews take longer because there's no context
  • Bisecting bugs becomes guesswork
  • Changelog generation turns into manual archaeology
  • New team members can't understand the project's history

I've reviewed thousands of PRs and written more commits than I care to count. Here's what actually works — practical rules you can start using today.

The 7 Rules of Good Commit Messages

These aren't theoretical. Every single one solves a real pain point.

1. Separate subject from body with a blank line

Bad:

fix the login bug and update the README while I was at it
Enter fullscreen mode Exit fullscreen mode

Good:

fix(auth): handle token expiration on 401 responses

The login flow silently fails when a cached token expires mid-session.
Now the interceptor catches 401 errors and triggers the refresh flow
before the user notices anything wrong.

Fixes #342
Enter fullscreen mode Exit fullscreen mode

Git can handle multi-line messages (git commit -m "subject" -m "body"). Use that power.

2. Limit the subject line to 50 characters

Your commit subject shows up everywhere: git log --oneline, GitHub PRs, Slack notifications, CI dashboards. Keep it scannable.

3. Capitalize the subject line

"add user routes" vs "Add user routes". Capitalize. It's a sentence.

4. Do not end the subject with a period

50 characters is already tight. Don't waste one on a period.

5. Use the imperative mood in the subject line

Think "This commit will...":

  • ✅ "Fix memory leak in cache layer"
  • ❌ "Fixed memory leak in cache layer"
  • ❌ "Fixes memory leak in cache layer"

The imperative matches Git's own convention (Merge branch 'main') and makes every commit read like an instruction, not a diary entry.

6. Wrap the body at 72 characters

Terminals are 80 columns wide. With 4-8 characters of indentation from git log, you have about 72 characters per line. Wrap manually for readability.

7. Use the body to explain what and why, not how

The code already shows how. Your message should answer:

  • What was the context? (bug report, feature request, refactor trigger)
  • Why was this approach chosen? (performance tradeoffs, alternatives considered)
  • What side effects should reviewers watch for?

Conventional Commits: Because Consistency > Creativity

If you want your team to write good messages consistently, adopt the Conventional Commits spec. It's simple:

<type>(<scope>): <subject>

<body>

<footer>
Enter fullscreen mode Exit fullscreen mode

Types that cover 90% of your commits:

Type When to use Example
feat New feature feat(api): add user registration endpoint
fix Bug fix fix(cache): invalidate token on expiry
docs Documentation docs(readme): add setup instructions
refactor Code change with no behavior change refactor(db): extract query builder
test Adding or fixing tests test(auth): add edge case for empty tokens
chore Maintenance tasks chore(deps): update lodash to 4.17.21

The beauty of Conventional Commits: they're parseable. Tools like standard-version, semantic-release, and GitHub Actions can auto-generate changelogs, bump versions, and publish releases based on your commit history.

Building the Habit Without the Pain

Knowing the rules and following them every time are two different things. Here's what actually works:

1. Use a prepare-commit-msg hook

Drop this into .git/hooks/prepare-commit-msg:

#!/bin/bash
echo ""
echo "# Please enter your commit message. Headers look like:"
echo "#   feat(scope): add user authentication"
echo "#   fix(scope): handle null pointer in parser"
echo "#   docs(readme): update API examples"
echo "#   refactor(db): extract query builder utility"
echo "#   test(scope): add regression test for issue #123"
echo "#   chore(deps): update dependency versions"
Enter fullscreen mode Exit fullscreen mode

It's just a reminder — but reminders work.

2. Set up a commit template

git config --global commit.template ~/.git-commit-template.txt
Enter fullscreen mode Exit fullscreen mode

Write a template file:


# <type>(<scope>): <subject>
# |<---- 50 chars max ---->|
#
# <body>
# |<---- 72 chars max ---->|
# 
# Why is this change needed?
# What approach was taken?
# Any side effects or risks?
Enter fullscreen mode Exit fullscreen mode

3. Automate it

I got tired of typing commit messages manually, so I built git-copilot — a tiny CLI tool that reads your staged changes and generates a conventional commit message instantly.

$ git add .
$ git-copilot gen
✨ feat(api): add user routes and controller
Enter fullscreen mode Exit fullscreen mode

No AI, no API keys, no internet. It uses smart pattern matching to detect the type, scope, and message from your changes. Free and open-source (MIT):

👉 git-copilot on GitHub

For teams that want multi-line body templates, CI/CD output formats, and breaking change tracking, there's a Pro Templates Pack on Gumroad ($9.99).

The Bottom Line

Good commit messages aren't pedantry — they're a force multiplier. A team that writes clear, consistent commits:

  • Reviews PRs 30-40% faster
  • Ships releases with auto-generated changelogs
  • Spends less time digging through git blame
  • Onboards new members in hours instead of days

Start with one rule today. Add another next week. By next month, "fixed stuff" will be a distant memory.


What's your team's commit message convention? Drop it in the comments — I'm always looking for new approaches.

Top comments (0)