Understanding commit messages
The future-self bug-hunting memo, in 50 characters.
The seven-rule Beams convention, Conventional Commits, and why the subject line is the only part most people will ever read.
The 50/72 rule.
Subject line ≤ 50 characters. Blank line. Body wrapped at 72 columns. The numbers come from git log's default terminal width and have been the convention since 2008. Modern tooling shows the subject line in pull-request lists, blame views, and one-line logs — anywhere a long subject gets truncated. Train yourself to write the short version first; the body can sprawl.
Imperative mood.
"Fix login bug", not "Fixed login bug" or "Fixes login bug". The convention reads well as a command: "If applied, this commit will [fix login bug]." Git itself uses imperative for its generated messages — Merge branch 'feature', not "Merged". The mismatch with the imperative convention is the easiest tell that a commit was written casually rather than for the log.
Conventional Commits.
The modern standard layers a structured prefix onto the subject line: feat:,fix:, docs:, refactor:, test:,chore:. Optional scope: feat(auth): add SSO support. Breaking changes get an exclamation: feat!: drop Node 14 support. Tooling (semantic-release, changelog generators) parses the prefix to decide the version bump: fix → patch, feat → minor, ! → major. The discipline pays back in automated release notes.
The body answers why.
The diff already shows what changed. The commit body should answer the questions the diff can't: why the change was needed, what alternatives were considered, what context a future bisecting developer needs. Reference issue numbers, link investigations, mention the root-cause analysis. A commit that says "Fix off-by-one" tells you nothing in three months; "Fix off-by-one in pagination; offset was applied twice when filters were active (#1234)" tells you everything.
A worked commit.
Subject: fix(api): handle null user in /me endpoint. Body: "The /me endpoint dereferenced req.user without first checking that the auth middleware had set it. Anonymous requests with a stale cookie therefore 500'd instead of 401'ing. Guard the dereference and return 401 explicitly. Repro: clear-cookie, hit /me, was 500, now 401. Fixes #4521." Reads as a self-contained bug report. Anyone bisecting a regression six months later has the full story without checking out the diff.
fix(api): handle null user
conventional commit + body
Subject ≤ 50 chars, body explains the why.
prefix + scope + imperative + reference to issue
= Self-contained bug report
What automation does with this.
Commit messages drive changelog generation (commitlint + semantic-release), per-PR release notes (Release Drafter), and semantic versioning in monorepos (Changesets). They also become the source of truth for incident write-ups and audit trails. The discipline of writing a real subject line means the changelog writes itself, your CI bumps versions correctly, and your future post-mortems quote real commits instead of "we fixed it somewhere around then".