Commit Message Format
A commit message is a factual record of one change. It tells the reader why the change happened. The reader has the diff
and the docs. The message adds only what the diff and the docs cannot show.
What Makes a Good Message
- Subject — only facts, in the imperative mood. It says what you did, not how good the change is.
- Body — it gives the reasons and the trade-offs. It stays objective. It has no promotional words.
Structure
[scope] subject
body
trailers
- Subject — what changed. The target is 50 characters. The hard limit is 72 characters.
- Body — the reason for the change, and the procedure that verifies it.
- Trailers — structured metadata such as references and authorship.
ASCII Symbols
- No em dash and no en dash (—, –). Use
-- or -.
- No arrows (→, ←, ↔). Use
->, <-, and <->.
- No bullet symbols (•, ▸). Use
- or *.
- No fancy quotes (“”, ‘’). Use straight quotes.
- No emoji (🐛, ✅, 🚀), unless the project conventions make them necessary.
- No other decorative symbols (©, ™, §, ¶, ∞).
Some environments break these symbols, and the symbols add no information.
Subject Line
- With a scope, for a monorepo:
[scope] <verb> <description>
- Without a scope, for a single-purpose repo:
<verb> <description>
Scope (Optional)
[parser] — a top-level project
[core/auth] — a nested path
[web/api] — a component in a project
Derive the scope from the file paths, not from the file contents. Use the common parent directory, or the most
significant component that you changed. Use one spelling for one component. [myapp/backend] and [myapp/b] in the
same log break the filtering that the scope exists for.
A path can hold a version directory such as v2. Omit the segment while one version is alive: write [core/engine],
because the reader has one candidate. Write [core/engine/v1] and [core/engine/v2] when both versions are alive,
because the segment is then the distinction that matters.
Body
- Why the change was necessary.
- What the subject cannot hold, such as a behavior change that surprises the reader.
- How a reader verifies it later, when that is not obvious. Give the steps that the reader repeats
(
run X, confirm Y). Never give the results that you produced in this session.
Wrap each body line at 72 characters. This is a hard limit. Git tools such as log, format-patch, and email expect
72-character body lines. Trailers are the only exception.
Shape
Each paragraph after the first earns its lines, or it goes. A paragraph earns its lines when it records a fact that the
diff and the docs cannot show:
- A second reason that the first paragraph does not carry.
- A
BREAKING: declaration, or a step of the migration path.
- A behavior change that the subject cannot predict, such as the new meaning of an absent value.
- A one-line note about the work that follows in the chain. Name that work. Do not write "more changes coming".
The reason itself depends on the kind of change:
- A bug fix — why the defect existed, not only what you corrected.
- A feature — the use case.
- A refactor — the motivation.
- A breaking change — the migration path.
The length follows the number of reasons, not the size of the diff. A refactor of 2000 lines with one reason gets
one paragraph. A migration with 6 steps gets the 6 steps. A body of 20 lines is correct only when 20 lines of reasons,
declarations, and steps exist. Such a body usually holds a walkthrough, or a second unit that belongs in its own commit.
More than one behavior change. One unit can change several behaviors. Give one line to each change, in one list. Do
not give a paragraph to each change:
- <behavior that the caller sees, present tense>.
- <behavior that the caller sees, present tense>
<continuation of the same item, indented two spaces>.
Mark each item with -. A wrapped line then keeps its item visible in git log. Each line gives the behavior that the
caller sees. Do not put the old behavior beside it as a contrast: the minus side of the diff carries the old behavior,
and git log carries the history. A line that names a file, a function, or a test inventories the diff. Delete such a
line.
The list does not license a longer body. Several behavior changes under one reason stay in one commit, and the reason
keeps its paragraph. Two independent reasons are two commits, whatever the number of behavior changes.
Record, Not Documentation
These signs show that a body became documentation:
- A paragraph for each decision inside the artifact. That is the design doc of the artifact.
- A walkthrough of the new behavior. A paragraph that names a function, a call order, an empty case, a fallback, or a
flag that gates the new code reports the procedure, and the diff shows the procedure.
- A list of the surfaces, the files, or the tests that the change touches. The diff already lists them.
- A repeat of what the updated docs say.
- A story of your verification, such as "verified by building X" or "measured on Y". That is a session artifact.
Two tests. Apply both to each paragraph.
- Paste the paragraph into the repo's docs. Does it stay true and useful there? Then it belongs in the repo's docs, not
in the message.
- Name the item from the earns-its-lines list above that the paragraph carries. A paragraph that matches no item is a
walkthrough. Delete it.
Terse Register
The diff carries the what. The message carries the why. A body can use fragments, and it can drop articles where the
meaning stays clear. Identifiers, file paths, and error strings stay exact.
The tense carries the timeline. State the current behavior in the plain present tense, because the commit itself
marks the "now". The past tense belongs to the cause of the change, and to nothing else:
Empty input dereferenced nil. Returns empty token list instead.
The old behavior is not the cause. A fix names the defect, because the diff cannot show why the code was wrong. A
behavior line names what the caller gets, and stops there. git log holds the history, and the diff holds the previous
code, so a before-and-after contrast in the message duplicates both.
Never include:
- "This commit does X", "This change...". The diff says what.
- "I", "we". The commit speaks for itself.
- "now", "currently", "previously". The tense already carries the timeline.
- The old behavior as a contrast: "X, where Y used to", "no longer does Y", "changed from Y to X". The diff holds the
previous code. Name the old behavior only as the cause of a fix, or as the thing that a
BREAKING: change removes.
- "As requested by...". Use a
Co-Authored-By: trailer, or write nothing.
- A walkthrough of the new behavior: the call order, the empty case, the fallback, the flag that gates it. A code
comment keeps such a fact, and the diff carries the comment into the history.
- The scope repeated:
[parser] update parser code becomes [parser] handle empty input.
- Promotional adjectives without data: "great", "amazing", "improved", "better". Say what is better.
- Decorative Unicode. See ASCII Symbols above.
- Filler: "just", "really", "basically", "actually", "simply". Connective fluff: "however", "furthermore",
"additionally".
- Session artifacts: test counts, lint status, typecheck status, build status, quality-gate status, CI results, and
verification stories. Examples: "55 of 55 tests passing", "all checks pass", "verified by building X". These record
your session at commit time, not the change. A future reader cannot verify them, and the next commit makes them stale.
Steps that a reader repeats are correct in the body. A log of the checks that you ran is not.
Breaking Changes
BREAKING: <what is broken>
<explanation and migration path>
Write the BREAKING: prefix in capital letters. Give a short description of what breaks after it. The paragraphs that
follow explain the reason and the migration path. Write a migration path of more than one step as a numbered list under
a Migration: line.
Trailers (Footer Metadata)
Trailers are structured key-value pairs at the end of the commit message. They use the
git-trailer format.
Task: — a link to the issue tracker — Task: https://tracker.example.com/PROJ-123
Fixes: — the issue that this commit corrects — Fixes: #456
Refs: — related issues or commits — Refs: #123, #124
Closes: — the issue that closes at merge time — Closes: #789
See: — related documentation — See: docs/auth.md
Reviewed-By: — the code reviewer — Reviewed-By: Alice <alice@example.com>
Co-Authored-By: — an additional author — Co-Authored-By: Bob <bob@example.com>
Write each trailer key in Title-Case. Keep each value on one line. For more than one value, use a comma-separated list,
or repeat the trailer.
Amending Commits
An amended commit replaces the original commit and rewrites the history. The final message must describe what the commit
introduces. It must not describe the sequence of revisions. From the reader's position there is no original commit.
There is only the resulting commit.
Common violation: the agent treats the amend as iterative work. It then describes the changes against the original
commit: "also update the tests", "fix the edge case from the previous version", "address review feedback". The reader
has no access to the original commit, and no context for "also" or "previous".
Correct approach: read the full staged diff after the amend. Then write a message that describes the complete change
as one introduction. Ignore the original message and start again.
No AI Attribution
- No "Generated with Claude Code" or a similar sentence
- No "Co-Authored-By: Claude" and no other AI co-author trailer
- No emoji that marks AI generation (🤖, and similar)
- No links to claude.com, anthropic.com, or AI tools
Commits must look like regular developer commits.
- Record, not documentation — the body carries the reason for the change. Rationale, invariants, and behavior
descriptions live in the artifact and travel in the diff
- Factual subjects — what changed, not how good it is
- Explain the cause — for a bug fix, say why the code was defective
- Body is essential — a message of one line is rarely acceptable. The default body is one paragraph
- No walkthrough — the body never narrates the new procedure: the call order, the empty case, the fallback, the flag
that gates it. The artifact keeps such a fact
- Length follows the reasons — not the size of the diff. Each paragraph after the first earns its lines, or it goes.
Two independent reasons usually mean two commits
- Terse register — the diff carries the what, the message carries the why. No "this commit", "I", or "we". The tense
carries the timeline. No filler. No promotional adjectives
- No session artifacts — test counts, lint status, CI status, gate status, and verification stories record your
session, not the change. "How to verify" means steps that the reader repeats
- BREAKING first — a breaking change starts the body with
BREAKING:
- Amends rewrite history — an amended message describes the full change, not the difference
- ASCII symbols — no decorative Unicode. Use the ASCII equivalents
- No AI attribution — commits look like regular developer work
1---2name: commit-message3description: Git commit message conventions: structure, formatting, scoping, body content, breaking changes, trailers.4---56# Commit Message Format78<scope>9These rules apply only to git commit messages. Do not apply the line-length limits, the format10rules, or the structure rules of this skill to other files. Code, documentation, and configuration11files keep their own rules.12</scope>1314A commit message is a factual record of one change. It tells the reader why the change happened. The reader has the diff15and the docs. The message adds only what the diff and the docs cannot show.1617<mental-model>18Write for the colleague who reads `git log` at 3am to find the defect. That colleague can open the19diff. That colleague cannot recover your reasons. Record the reasons.20</mental-model>2122## What Makes a Good Message2324<context-principle>25Draw the _why_ from three sources: the task that you solved, the reason that this approach won, and26the code that you built. Do not copy the sources into the message. If the context is incomplete,27record what the code and the available information can verify.28</context-principle>2930<factual-principle>31A commit message is a **factual record**. It is not marketing copy. The subject answers this32question: what did this commit do? It does not answer this question: why is this commit good?3334- **Subject** — only facts, in the imperative mood. It says what you did, not how good the change is.35- **Body** — it gives the reasons and the trade-offs. It stays objective. It has no promotional words.3637</factual-principle>3839## Structure4041<format>42The message has three blocks. A blank line separates the blocks:4344```text45[scope] subject4647body4849trailers50```5152- **Subject** — what changed. The target is 50 characters. The hard limit is 72 characters.53- **Body** — the reason for the change, and the procedure that verifies it.54- **Trailers** — structured metadata such as references and authorship.5556</format>5758### ASCII Symbols5960<charset>61Use ASCII punctuation and ASCII symbols only. Prose in a different language is correct. This rule62applies to decorative and typographic Unicode, not to natural language.6364- No em dash and no en dash (—, –). Use `--` or `-`.65- No arrows (→, ←, ↔). Use `->`, `<-`, and `<->`.66- No bullet symbols (•, ▸). Use `-` or `*`.67- No fancy quotes (“”, ‘’). Use straight quotes.68- No emoji (🐛, ✅, 🚀), unless the project conventions make them necessary.69- No other decorative symbols (©, ™, §, ¶, ∞).7071Some environments break these symbols, and the symbols add no information.7273</charset>7475## Subject Line7677- With a scope, for a monorepo: `[scope] <verb> <description>`78- Without a scope, for a single-purpose repo: `<verb> <description>`7980<subject-rules>81- Write 50 characters or less. The hard limit is 72 characters.82- Use the imperative mood: "add", not "added".83- Use lowercase after the scope. Proper nouns keep their capital letters.84- Do not put a period at the end.85- **Write only facts.** Say what changed, not how good the change is.86- **Remove the filler words.** Drop "this commit", "I", "we", "now", and "currently". Never repeat the scope.8788</subject-rules>8990### Scope (Optional)9192<scope-guidance>93Use a scope when the repo holds more than one project, package, or subsystem, or when readers filter94`git log` by component. Omit the scope when the repo has one purpose, or when the scope is always95the same.9697- `[parser]` — a top-level project98- `[core/auth]` — a nested path99- `[web/api]` — a component in a project100101Derive the scope from the file paths, not from the file contents. Use the common parent directory, or the most102significant component that you changed. Use one spelling for one component. `[myapp/backend]` and `[myapp/b]` in the103same log break the filtering that the scope exists for.104105A path can hold a version directory such as `v2`. Omit the segment while one version is alive: write `[core/engine]`,106because the reader has one candidate. Write `[core/engine/v1]` and `[core/engine/v2]` when both versions are alive,107because the segment is then the distinction that matters.108109</scope-guidance>110111## Body112113<body-philosophy>114A message of one line is rarely correct. The body is the channel to future readers, and it answers115three questions:116117- **Why** the change was necessary.118- **What** the subject cannot hold, such as a behavior change that surprises the reader.119- **How a reader verifies it** later, when that is not obvious. Give the steps that the reader repeats120 (`run X, confirm Y`). Never give the results that you produced in this session.121122Wrap each body line at 72 characters. This is a hard limit. Git tools such as `log`, `format-patch`, and email expect12372-character body lines. Trailers are the only exception.124125</body-philosophy>126127### Shape128129<body-shape>130The default body is one paragraph of 2 to 5 lines. It names the cause, then the new behavior.131132Each paragraph after the first earns its lines, or it goes. A paragraph earns its lines when it records a fact that the133diff and the docs cannot show:134135- A second reason that the first paragraph does not carry.136- A `BREAKING:` declaration, or a step of the migration path.137- A behavior change that the subject cannot predict, such as the new meaning of an absent value.138- A one-line note about the work that follows in the chain. Name that work. Do not write "more changes coming".139140The reason itself depends on the kind of change:141142- A bug fix — why the defect existed, not only what you corrected.143- A feature — the use case.144- A refactor — the motivation.145- A breaking change — the migration path.146147**The length follows the number of reasons, not the size of the diff.** A refactor of 2000 lines with one reason gets148one paragraph. A migration with 6 steps gets the 6 steps. A body of 20 lines is correct only when 20 lines of reasons,149declarations, and steps exist. Such a body usually holds a walkthrough, or a second unit that belongs in its own commit.150151**More than one behavior change.** One unit can change several behaviors. Give one line to each change, in one list. Do152not give a paragraph to each change:153154```text155- <behavior that the caller sees, present tense>.156- <behavior that the caller sees, present tense>157 <continuation of the same item, indented two spaces>.158```159160Mark each item with `-`. A wrapped line then keeps its item visible in `git log`. Each line gives the behavior that the161caller sees. Do not put the old behavior beside it as a contrast: the minus side of the diff carries the old behavior,162and `git log` carries the history. A line that names a file, a function, or a test inventories the diff. Delete such a163line.164165The list does not license a longer body. Several behavior changes under one reason stay in one commit, and the reason166keeps its paragraph. Two independent reasons are two commits, whatever the number of behavior changes.167168</body-shape>169170### Record, Not Documentation171172<record-not-documentation>173The body records why this change happened. It does not document the result. The changed files174document the result. Keep a rationale, an invariant, or a behavior description in the artifact: a175name, a test, a doc comment, a design doc, or the README. The diff then carries it into the history.176177These signs show that a body became documentation:178179- A paragraph for each decision inside the artifact. That is the design doc of the artifact.180- A walkthrough of the new behavior. A paragraph that names a function, a call order, an empty case, a fallback, or a181 flag that gates the new code reports the procedure, and the diff shows the procedure.182- A list of the surfaces, the files, or the tests that the change touches. The diff already lists them.183- A repeat of what the updated docs say.184- A story of your verification, such as "verified by building X" or "measured on Y". That is a session artifact.185186**Two tests. Apply both to each paragraph.**1871881. Paste the paragraph into the repo's docs. Does it stay true and useful there? Then it belongs in the repo's docs, not189 in the message.1902. Name the item from the earns-its-lines list above that the paragraph carries. A paragraph that matches no item is a191 walkthrough. Delete it.192193</record-not-documentation>194195### Terse Register196197The diff carries the _what_. The message carries the _why_. A body can use fragments, and it can drop articles where the198meaning stays clear. Identifiers, file paths, and error strings stay exact.199200**The tense carries the timeline.** State the current behavior in the plain present tense, because the commit itself201marks the "now". The past tense belongs to the cause of the change, and to nothing else:202`Empty input dereferenced nil. Returns empty token list instead.`203204**The old behavior is not the cause.** A fix names the defect, because the diff cannot show why the code was wrong. A205behavior line names what the caller gets, and stops there. `git log` holds the history, and the diff holds the previous206code, so a before-and-after contrast in the message duplicates both.207208**Never include:**209210- "This commit does X", "This change...". The diff says what.211- "I", "we". The commit speaks for itself.212- "now", "currently", "previously". The tense already carries the timeline.213- The old behavior as a contrast: "X, where Y used to", "no longer does Y", "changed from Y to X". The diff holds the214 previous code. Name the old behavior only as the cause of a fix, or as the thing that a `BREAKING:` change removes.215- "As requested by...". Use a `Co-Authored-By:` trailer, or write nothing.216- A walkthrough of the new behavior: the call order, the empty case, the fallback, the flag that gates it. A code217 comment keeps such a fact, and the diff carries the comment into the history.218- The scope repeated: `[parser] update parser code` becomes `[parser] handle empty input`.219- Promotional adjectives without data: "great", "amazing", "improved", "better". Say what is better.220- Decorative Unicode. See ASCII Symbols above.221- Filler: "just", "really", "basically", "actually", "simply". Connective fluff: "however", "furthermore",222 "additionally".223- Session artifacts: test counts, lint status, typecheck status, build status, quality-gate status, CI results, and224 verification stories. Examples: "55 of 55 tests passing", "all checks pass", "verified by building X". These record225 your session at commit time, not the change. A future reader cannot verify them, and the next commit makes them stale.226 Steps that a reader repeats are correct in the body. A log of the checks that you ran is not.227228## Breaking Changes229230<breaking-changes>231A commit can break backward compatibility. The body of such a commit MUST start with a `BREAKING:`232declaration as the first paragraph:233234```text235BREAKING: <what is broken>236237<explanation and migration path>238```239240Write the `BREAKING:` prefix in capital letters. Give a short description of what breaks after it. The paragraphs that241follow explain the reason and the migration path. Write a migration path of more than one step as a numbered list under242a `Migration:` line.243244</breaking-changes>245246## Trailers (Footer Metadata)247248Trailers are structured key-value pairs at the end of the commit message. They use the249[git-trailer format](https://git-scm.com/docs/git-interpret-trailers).250251<trailers>252A blank line separates the body from the trailers. Each trailer stays on its own line, in the form253`Key-Name: value`.254255- `Task:` — a link to the issue tracker — `Task: https://tracker.example.com/PROJ-123`256- `Fixes:` — the issue that this commit corrects — `Fixes: #456`257- `Refs:` — related issues or commits — `Refs: #123, #124`258- `Closes:` — the issue that closes at merge time — `Closes: #789`259- `See:` — related documentation — `See: docs/auth.md`260- `Reviewed-By:` — the code reviewer — `Reviewed-By: Alice <alice@example.com>`261- `Co-Authored-By:` — an additional author — `Co-Authored-By: Bob <bob@example.com>`262263Write each trailer key in Title-Case. Keep each value on one line. For more than one value, use a comma-separated list,264or repeat the trailer.265266</trailers>267268## Amending Commits269270<amend-rule>271When you amend a commit with `git commit --amend`, **write the message again as if the commit is272new**.273274An amended commit replaces the original commit and rewrites the history. The final message must describe what the commit275introduces. It must not describe the sequence of revisions. From the reader's position there is no original commit.276There is only the resulting commit.277278**Common violation:** the agent treats the amend as iterative work. It then describes the changes against the original279commit: "also update the tests", "fix the edge case from the previous version", "address review feedback". The reader280has no access to the original commit, and no context for "also" or "previous".281282**Correct approach:** read the full staged diff after the amend. Then write a message that describes the complete change283as one introduction. Ignore the original message and start again.284285</amend-rule>286287## No AI Attribution288289<forbidden>290**Never add a reference that indicates AI involvement:**291292- No "Generated with Claude Code" or a similar sentence293- No "Co-Authored-By: Claude" and no other AI co-author trailer294- No emoji that marks AI generation (🤖, and similar)295- No links to claude.com, anthropic.com, or AI tools296297Commits must look like regular developer commits.298299</forbidden>300301<critical>302## Core Principles303304- **Record, not documentation** — the body carries the reason for the change. Rationale, invariants, and behavior305 descriptions live in the artifact and travel in the diff306- **Factual subjects** — what changed, not how good it is307- **Explain the cause** — for a bug fix, say why the code was defective308- **Body is essential** — a message of one line is rarely acceptable. The default body is one paragraph309- **No walkthrough** — the body never narrates the new procedure: the call order, the empty case, the fallback, the flag310 that gates it. The artifact keeps such a fact311- **Length follows the reasons** — not the size of the diff. Each paragraph after the first earns its lines, or it goes.312 Two independent reasons usually mean two commits313- **Terse register** — the diff carries the what, the message carries the why. No "this commit", "I", or "we". The tense314 carries the timeline. No filler. No promotional adjectives315- **No session artifacts** — test counts, lint status, CI status, gate status, and verification stories record your316 session, not the change. "How to verify" means steps that the reader repeats317- **BREAKING first** — a breaking change starts the body with `BREAKING:`318- **Amends rewrite history** — an amended message describes the full change, not the difference319- **ASCII symbols** — no decorative Unicode. Use the ASCII equivalents320- **No AI attribution** — commits look like regular developer work321322</critical>