name: commit-message
description: Write and apply Git commit messages. Use this skill whenever committing code, staging a commit, writing a commit message, or when the user says "commit", "git commit", "save my changes", or asks to describe what changed. Also trigger when reviewing staged changes before committing. This skill enforces file-based commit messages — never pass messages via -m.
Commit Message Creation
Write commit messages to a temporary file and commit with git commit -F. Never use git commit -m.
Workflow
- Examine the diff (staged or working tree)
- Write the message to a temp file
- Commit using that file
- Clean up
# 1. Inspect what's staged (or stageable)
git diff --cached # staged changes
git diff # unstaged changes (for context)
git status --short # overview
# 2. Write the message
COMMIT_MSG_DIR=$(mktemp -d)
cat > "$COMMIT_MSG_DIR/COMMIT_MSG.md" << 'ENDOFMSG'
<summary line>
<body>
ENDOFMSG
# 3. Commit
git commit -F "$COMMIT_MSG_DIR/COMMIT_MSG.md"
# 4. Clean up
rm -rf "$COMMIT_MSG_DIR"
Message Format
The commit message is a Markdown file with two parts: a summary line and an optional body separated by a blank line.
Summary Line
- Imperative mood ("Add search endpoint", not "Added search endpoint" or "Adds search endpoint")
- No conventional-commit prefixes — no
fix(scope):,feat:,chore:, or any variant - No trailing full stop
- ≤ 72 characters
- If the commit addresses a GitHub issue, append the issue number in parentheses at the end:
Enforce rate limiting on public API (#42)
Body
- Wrap at 72 characters
- Write in Markdown — use backticks for identifiers, fenced blocks for code, lists where they aid clarity
- Imperative mood throughout ("Remove the fallback", not "Removed the fallback")
- Explain what changed and why, not how (the diff shows how)
- Reference related issues or PRs where relevant
Attribution
Do not append any attribution line. No "Co-authored-by", "Signed-off-by", "Generated by", or similar trailer attributing the message to an AI, a bot, or any non-human agent. The commit message describes the change; it does not advertise the toolchain.
Hard Rules
These are non-negotiable. Violating any of them means the message is wrong.
| Rule | Rationale |
|---|---|
Never use git commit -m or git commit -m "..." | Multi-line messages are unreadable as shell arguments. The file-based workflow avoids quoting issues, supports proper formatting, and encourages thoughtful messages. |
Always write to a mktemp -d directory | Avoids polluting the working tree or repo root with scratch files. |
| Imperative mood in summary and body | Convention established by Git itself (Merge branch ..., Revert "..."). |
| No conventional-commit prefixes | The summary describes the change in plain English. Prefixes add taxonomy noise that belongs in labels, changelogs, or CI config — not in the commit log. |
| No AI/bot attribution trailers | The commit records what changed, not who typed it. |
| Message file is Markdown | Enables backtick formatting for identifiers and fenced code blocks when referencing specific symbols or snippets. |
Why a file, not a string
It is tempting to "oneline" the message into git commit -m or to pipe a heredoc into git commit -F -. Resist. Both approaches reliably produce embarrassing artefacts: the shell expands backticked code spans as command substitutions, $variable references get interpolated, nested quotes break in ways that depend on the outer quoting style, and attempts to embed literal newlines scatter \n\n through the log. A here-document with a quoted delimiter (<< 'EOF') avoids some of these, but still leaves the message buried in a pipeline where it cannot be inspected before it lands.
Writing to a file in a mktemp -d directory is the boring option. It is also the correct one. The message is visible on disk before the commit, trivially reviewable, immune to shell expansion, and cleaned up afterwards. Better to be correct than to be clever.
Deciding What to Say
Read the diff carefully. Ask:
- What is the net effect on the system's behaviour? Lead with this.
- Why was this change necessary? Include if not obvious from the summary.
- What alternatives were considered or rejected? Include if it saves a future reader from retreading the same ground.
- Are there non-obvious consequences? Migration steps, deprecations, perf implications — mention them.
For trivial changes (typo fixes, import reordering), a summary line alone suffices. Do not pad with filler.
Examples
Single-line (trivial fix)
Fix null dereference in session cleanup
With issue reference
Add request deduplication to webhook handler (#137)
Multi-line (non-trivial change)
Replace hand-rolled LRU cache with `lru-cache` crate
The previous implementation had an off-by-one in eviction that surfaced
under concurrent access. Rather than fix the edge case, switch to a
well-tested upstream crate.
`lru-cache` 0.6 supports `Send + Sync` out of the box, so the
`Arc<Mutex<...>>` wrapper around the old cache is no longer necessary.
Multi-line with issue
Enforce rate limiting on public API endpoints (#42)
Apply a sliding-window rate limiter (100 req/min per API key) to all
routes under `/v1/public`. Exceeding the limit returns `429 Too Many
Requests` with a `Retry-After` header.
Internal endpoints under `/v1/admin` remain unlimited. The limiter
stores counters in Redis, keyed by `rl:{api_key}:{window}`.