Skip to content
ExponaBLOG
GIT & GITHUB FOR PROFESSIONAL DEVELOPERS · DAY 005 / 100

Writing good commit messages for Git & GitHub developers

Messages your future self will thank you for.

Expona Admin6 min read

Six months from now, you'll be hunting through your Git history trying to understand why a critical piece of logic was changed. You'll find a commit message that reads "update stuff" and spend the next hour digging through code diffs to figure out what problem it solved. Writing good commit messages for Git & GitHub developers isn't about following arbitrary rules—it's about leaving a trail of breadcrumbs that your future self, your teammates, and your code reviewers will actually want to follow.

The difference between a project where you can understand the history and one where you can't often comes down to one thing: commit message discipline. A well-crafted commit message is documentation that lives forever in your repository, survives refactors, and helps you understand not just what changed, but why.

The anatomy of a commit message: Short summary under 50 characters

Your first line is the headline. It's what appears in logs, on GitHub's commit list, and in pull request summaries. It needs to be crisp, specific, and instantly scannable. Fifty characters isn't a hard law, but it's a practical guideline that forces you to be precise.

Think about what you're actually telling the team. Instead of "fix bug" or "update code," you're describing a specific change. Here's the difference:

  • Bad: "fix bug in auth" (too vague)
  • Better: "Fix JWT expiration not refreshing on token claim" (specific and clear)
  • Bad: "update database query" (what database? what query?)
  • Better: "Optimize user_accounts index on email column" (tells you what and where)

When you're writing that first line, imagine your teammate searching the commit history for "when did we change the email validation?" If your subject line is generic, your teammate will have to open dozens of commits to find the answer. If it's specific, they'll know in seconds.

The fifty-character limit also serves a practical purpose: it's what fits in most Git logs and GitHub's UI without truncation. Long subject lines look unprofessional and force readers to click through to see the full message.

Imperative mood: Add, Fix, Remove

This is the convention that makes the biggest difference to readability. Write your commit message as if you're giving a command to the codebase: "Add," "Fix," "Remove," "Refactor," "Update." Not "Added," "Fixed," or "Updates."

Here's why this matters: when you read your commit history as a series of commands, it becomes a log of actions taken. It reads naturally. It's also the standard across most open-source projects and large teams, which means anyone who reviews your code will immediately understand your style.

Some examples in practice:

  • "Add retry logic to Stripe webhook handler" (clear action)
  • "Fix null reference in cart calculation service" (specific fix)
  • "Remove deprecated API endpoint for v1 users" (clean removal)
  • "Refactor password hashing to use argon2" (clear what you did and why)
  • "Update Redis connection pool timeout to 5s" (specific change)

Compare this to the passive voice alternatives: "Added retry logic," "Fixed null reference," "Removed deprecated API." Those work, but they feel less commanding and less immediate. The imperative mood keeps your commit history reading like a series of deliberate actions rather than a log of past events.

This convention is so standard that some teams enforce it in their Git hooks. Others mention it in pull request templates. Either way, adopting it early means your code reviews will focus on substance, not style corrections.

Explain why in the body

Your subject line answers "what." The body answers "why." This is where senior engineers separate themselves from the pack.

The body of a commit message should explain the reasoning behind the change. Why did you choose this solution? What problem does it solve? What trade-offs did you consider? This context is invaluable when someone—including your future self—tries to understand the history of a piece of code.

Here's a realistic example. Say you've optimized a database query. Your subject line is "Optimize user_accounts index on email column." But the body might say:

Email lookups were scanning the full table, causing 2s latency on the login endpoint during peak hours. Created a btree index on the email column to speed up exact-match queries.

Tested with production dataset: query time dropped from 2000ms to 15ms. No performance regression on writes.

Now someone reading this commit in six months understands not just that an index was added, but why it mattered and how it was validated. If they later wonder "can we remove this index?" they'll know it was added to solve a specific performance problem, not as a random optimization.

Another example: fixing a tricky bug:

Fix race condition in payment processing when simultaneous requests arrive within 10ms

The issue occurred because we were checking balance after retrieving from cache, but cache could be stale between check and debit. Added a lock on the user account record during the balance check and debit sequence.

Added regression test: test_concurrent_payment_requests_same_user

This tells the next person who touches this code exactly what the problem was, how you fixed it, and how to verify it doesn't regress. That's professional work.

The format is simple: blank line after your subject, then paragraph breaks between logical sections. Keep lines under 72 characters in the body (a Git convention that helps readability in various tools).

Reference issue numbers

If your commit is related to a GitHub issue, Jira ticket, or any tracked work item, reference it. This creates a link between your code and the conversation that led to it.

The format is straightforward:

  • At the end of your commit message, add "Fixes #123" or "Closes #456"
  • GitHub automatically links this and closes the issue when the commit lands on the default branch
  • If the commit relates to an issue but doesn't fully close it, use "Related to #123" instead

Example commit message with issue reference:

bash
Add exponential backoff to Email delivery service

The service was failing on temporary SMTP timeouts without retry.
Added exponential backoff with jitter to handle transient failures.

Backoff strategy: 1s, 2s, 4s, 8s, 16s max with ±10% random jitter.
Tested against mock SMTP server with connection timeouts.

Fixes #478

When this commit lands on main, GitHub will automatically close issue #478 and link the commit to the conversation. Anyone reading the issue later will see exactly what code addressed it. Anyone reading the commit will understand what problem it solved.

If you're using a different tracking system (Jira, Linear, etc.), follow your team's conventions. The principle is the same: create a traceable link between work items and the code that implements them.

Common mistakes

We've seen enough commit histories to know the patterns that make code review harder:

Vague subject lines. "Fix stuff," "update code," "changes" tell you nothing. Your future self won't thank you.

Subject lines that are actually the body. "Fix the issue where the authentication token wasn't refreshing correctly in the JWT handler when the user had multiple browser tabs open and switched between them" is a paragraph, not a subject line. Cut it down to the essential fact: "Fix JWT token refresh in multi-tab sessions."

No explanation of why. Leaving the body empty means readers have to infer your reasoning from diffs. That's wasted context.

Committing fixes and features together. A commit should be atomic and focused. "Fix bug in auth and add new password policy" is two commits, not one. This makes it easier to revert specific changes if needed and keeps your history clean.

Forgetting issue references. If your team tracks work, linking commits to issues keeps everything connected. Skipping this makes it harder to trace decisions later.

Writing in past tense or stream of consciousness. "I fixed the bug by updating the query handler then tested it" isn't professional. "Fix authentication query handler performance" is.

Assuming the diff speaks for itself. The diff shows what changed. Your message explains why it mattered. Both are necessary.

Key takeaways

  • Keep your subject line under 50 characters and specific enough that teammates can find commits by keywords
  • Use imperative mood (Add, Fix, Remove) to make your commit history read as a series of deliberate actions
  • Write a body that explains the reasoning, trade-offs, and validation—not a restatement of the diff
  • Reference issue numbers so commits are traceable back to the work that motivated them
  • Treat commit messages as documentation that will be read by many people, many times, over months and years
  • One focused change per commit keeps history clean and makes it easier to revert or cherry-pick changes later
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles

Writing good commit messages | Git & GitHub | Expona