commit-guard
Conventional commit linting with imperative mood detection.
$ commit-guard --range origin/main..HEAD
abc1234 feat: add user authentication
✓ all checks passed
def5678 wip: still working
✗ [subject] unknown type: wip
✗ [body] missing body
Install #
uv tool install git-commit-guard
pipx install git-commit-guard
pip install git-commit-guard
Or via pre-commit.
Quick start #
# check HEAD
$ commit-guard
# check a specific commit
$ commit-guard abc1234
# check all commits in a PR range
$ commit-guard --range origin/main..HEAD
# read from a file (for git hooks)
$ commit-guard --message-file .git/COMMIT_EDITMSG
# pipe message via stdin
$ echo "fix(auth): add token refresh" | commit-guard
Checks
All checks run by default. Enable or disable individually with
--enable / --disable:
| Check | What it verifies |
|---|---|
subject |
Conventional Commits
format: valid type, lowercase start, no trailing period,
max length (default 72). All limits are configurable. Use
! before the colon for breaking changes:
feat!: remove endpoint
|
imperative |
First word is an imperative verb — uses NLP, not just a regex |
body |
Blank line separates subject from body, and body is non-empty |
signed-off |
Signed-off-by: trailer is present |
signature |
GPG or SSH signature is valid |
Configuration #
Place .commit-guard.toml in your project root or any
parent directory — commit-guard searches upward and uses the first
file found. CLI flags always take precedence.
# .commit-guard.toml
disable = ["signature", "body"]
scopes = ["auth", "api", "db"]
require-scope = true
types = ["feat", "fix", "chore", "wip"]
max-subject-length = 100
min-description-length = 10
require-trailers = ["Closes", "Reviewed-by"]
Required trailers
Require arbitrary trailers in the commit message. Accepts a
comma-separated list; matching is case-sensitive and requires a
non-empty value after the colon (e.g. Closes: #42):
commit-guard --require-trailer "Closes,Reviewed-by"
Range options
When using --range, merge commits are excluded by
default. Use --include-merges to check them. An empty
range exits non-zero by default — use --allow-empty to
exit 0 instead:
commit-guard --range origin/main..HEAD --include-merges --allow-empty
Environment variables
| Variable | Default | Description |
|---|---|---|
COMMIT_GUARD_GIT_TIMEOUT |
10 |
Timeout in seconds for git subprocess calls |
Output #
Use --output jsonl to emit one JSON line per commit to
stdout instead of the default human-readable text:
commit-guard --range origin/main..HEAD --output jsonl | jq 'select(.ok == false)'
Each line is a JSON object:
{
"sha": "abc1234...",
"subject": "feat: add thing",
"ok": false,
"results": [{"check": "body", "level": "error", "message": "missing body"}]
}
sha is null when reading from a file or
stdin. results is empty when all checks pass.
Use --output-file FILE to write JSONL to a file while
keeping human-readable text on stdout — useful in CI where you want
readable logs and structured results for downstream steps:
commit-guard --range origin/main..HEAD --output-file results.jsonl
GitHub Actions #
Check all commits in a pull request:
jobs:
lint-commits:
runs-on: ubuntu-latest
env:
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: benner/commit-guard@v0.16.0
with:
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
disable: signed-off,signature
Check a specific commit SHA:
- uses: benner/commit-guard@v0.16.0
with:
rev: ${{ github.sha }}
All inputs mirror the CLI flags: rev,
range, enable, disable,
scopes, require-scope, types,
max-subject-length, min-description-length,
require-trailer, allow-empty,
include-merges, output-file.
When output-file is set the action exposes the path as
a step output, making JSONL results available to subsequent steps:
- uses: benner/commit-guard@v0.16.0
id: cg
with:
range: ${{ env.PR_BASE }}..${{ env.PR_HEAD }}
output-file: results.jsonl
- run: jq 'select(.ok == false)' "${{ steps.cg.outputs.output-file }}"
pre-commit #
Add to .pre-commit-config.yaml:
repos:
- repo: https://github.com/benner/commit-guard
rev: v0.16.0
hooks:
- id: commit-guard
- id: commit-guard-signature