Git Commit Hygiene skill for Codex
Atomic commits a reviewer can read in order. Codex plans a commit series before staging, keeps refactors and behaviour changes apart, and stages partial files through git apply --cached because interactive add hangs an agent shell. Messages follow the 50/72 convention with real reasons and trailers, and git rebase --exec proves every commit in the series builds and passes tests.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- git-commit-hygiene Installs to ~/.agents/skills/git-commit-hygiene/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install git-commit-hygiene The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project git-commit-hygiene Installs to .agents/skills/git-commit-hygiene/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/git-commit-hygiene.zipunzip -o git-commit-hygiene.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) git-commit-hygiene PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $git-commit-hygiene, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Turns a messy working tree into a sequence of atomic, buildable commits with messages that explain why - reviewing git status, staging selected hunks without interactive prompts via git apply --cached, separating refactors from behaviour changes, writing subject and body to the 50/72 convention with trailers, and checking each commit with git rebase --exec. Use when asked to commit work, split changes into logical commits, clean up before a PR, or write a better commit message.
Use it when
- Work is finished and needs committing in reviewable pieces
- One file contains changes for more than one purpose
- A commit message is vague or describes the diff instead of the reason
Use something else when
- The repo enforces type(scope): headers and commitlint: use conventional-commits
- Commits already exist and need reordering, squashing or splitting: use git-history-surgery
How to check the result
git rebase --exec with the test command passes on every commit in the series, and git status shows nothing unexpected left behind.
The mistake it prevents
Running git add -A and sweeping editor files, build output or a local .env into a commit.
Each commit does one thing, builds and passes tests on its own, and has a message a
stranger can understand in a year: what changed in the subject, why in the body. A
reviewer can read the series commit by commit, and git revert of any one commit is safe.
Before committing anything
- Commit only when the user asked for commits. Read
AGENTS.mdfor message format (Conventional Commits, ticket prefixes, sign-off) and follow it over these defaults. - Look at the whole tree, not just what you remember editing:
git status --porcelain=v1
git diff --stat
git diff --cached --stat # anything already staged by someone else?
- Never
git add -Aorgit add .blindly. Unexpected files (.env, editor swap files, build output, large binaries, credentials) must be left out and mentioned. - Check for whitespace damage and conflict markers:
git diff --check.
Plan the commit sequence
Group changes by intent, then order them so every prefix of the series builds:
| Change kind | Own commit? | Goes |
|---|---|---|
| Pure rename / move | Yes | First, so later diffs are readable |
| Mechanical reformat | Yes, never mixed with logic | First, or skip if unrelated to the task |
| Refactor with no behaviour change | Yes | Before the feature that needs it |
| New behaviour plus its tests | Yes, together | After its preparatory refactors |
| Dependency bump | Yes | Before code that relies on it |
| Generated files (lockfile, codegen) | With the change that caused them | Same commit as the source change |
| Unrelated fix spotted on the way | Separate commit, or a note for later | Ask the user if it is out of scope |
Write the plan as a short list before staging, for example:
1. refactor(cart): extract tier_for() from compute_total src/cart.py
2. feat(cart): add bulk pricing tiers src/cart.py, tests/test_cart.py
3. docs: document bulk pricing docs/pricing.md
Staging precisely without interactive prompts
git add -p waits for keyboard input, which a non-interactive Codex shell cannot give
reliably. Stage whole files with git add <path> when a file belongs to one commit.
When one file holds changes for two commits, stage a hunk subset through a patch:
git diff -- src/cart.py > "$TMPDIR/cart.full.patch"
# Copy the file header (diff --git, index, ---, +++) and only the hunks for commit 1
# into cart.part1.patch. Keep each kept hunk intact, from its @@ line to the next @@.
git apply --cached --check --recount "$TMPDIR/cart.part1.patch"
git apply --cached --recount "$TMPDIR/cart.part1.patch"
git diff --cached -- src/cart.py # confirm exactly the intended hunks are staged
git diff -- src/cart.py # and the rest is still unstaged
If two intents share a single hunk, regenerate the diff with less context
(git diff -U1) to split it, or temporarily edit the file to the intermediate state,
stage it, then restore the final version from a saved copy.
Useful undo commands while staging:
git restore --staged <path> # unstage, keep working-tree changes
git reset --soft HEAD~1 # undo last commit, keep it staged (local only)
Writing the message
Cap bulk discount at the highest single tier
Tiers were applied cumulatively, so 100 units received 5% and then 12%
off the discounted price. Finance confirmed tiers must not stack.
Pick the single highest tier the quantity qualifies for. Totals for
orders under 10 units are unchanged.
Refs: #812
- Subject: imperative mood (“Add”, “Fix”, “Remove”), about 50 characters, hard limit 72, no trailing period. It completes “If applied, this commit will …”.
- Blank line, then a body wrapped at 72 columns. Explain the problem, why this approach, and any consequence a reviewer would not see in the diff (migration needed, behaviour change, performance trade-off). Skip the body only for truly self-evident commits.
- Do not narrate the diff (“changed line 42”), do not write “various fixes”, “WIP”, “address comments”, or “update file.py”.
- Trailers go last, one per line:
Refs:,Fixes #123(closes the issue on GitHub when merged to the default branch),Co-authored-by: Name <email>,Signed-off-by:when the repo uses DCO (git commit -s).
Pass multi-line messages without an editor:
git commit -F "$TMPDIR/msg.txt"
git commit -m "Cap bulk discount at the highest single tier" -m "Tiers were applied ..." \
--trailer "Refs: #812"
Verify the series
Check that every commit builds and passes tests, not just the tip:
git log --oneline --reverse origin/main..HEAD
git rebase --exec "npm test --silent" origin/main # runs after each commit; stops on failure
The rebase refuses to start with unstaged changes; add --autostash if you deliberately
left files out. --exec leaves the commits unchanged when every command passes. If one fails, the rebase stops at that
commit: fix it, git commit --amend, then git rebase --continue. Abort with
git rebase --abort if the fix belongs elsewhere.
Final checks:
-
git statusis clean or shows only files you deliberately left out (and reported). -
git log --stat origin/main..HEADshows no stray files. - Each subject reads as an imperative sentence; no “WIP” or “fixup!” left unless the user wants them squashed later (see git-history-surgery).
- No secrets:
git diff origin/main..HEAD | rg -n "(AKIA|BEGIN .*PRIVATE KEY|api[_-]?key\s*=)".
Hard rules
- Never commit with
--no-verifyto get past hooks unless the user explicitly asks; fix what the hook reports. - Never amend or rewrite commits that are already pushed to a shared branch without the user’s confirmation; that requires a force push.
- Never commit secrets. If one was committed, stop and tell the user: removing it from history is not enough, the secret must be rotated.
- Do not change author identity or
user.emailconfig.
Output
After committing, report:
Created 3 commits on feature/bulk-pricing (not pushed):
a1b2c3d refactor(cart): extract tier_for() from compute_total
d4e5f6a feat(cart): add bulk pricing tiers
0718293 docs: document bulk pricing
Each commit passes `npm test` (checked with rebase --exec).
Left unstaged: .env.local (local config, not part of this change). What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Git Commit Hygiene"
short_description: "Atomic commits a reviewer can read in order"
default_prompt: "$git-commit-hygiene Commit my changes as a few logical commits instead of one big one" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.