commitpreflight
Pre-flight lint for commit messages written by AI coding agents.
Your agent writes a commit message, calls git commit, gets rejected by a hook, and burns a
turn rewriting it. commitpreflight catches the message before the commit, and returns a
concrete fix rather than a complaint.
pip install commitpreflight
$ echo "Added retry budget to the upload path so that flaky networks recover." | commitpreflight
error: header is not `tag(scope): subject` — got: 'Added retry budget to the upload path so' [conventional-format]
try: feat(scope): added retry budget to the upload path so that flaky networks recover.
error: subject must not end with a period [subject-punctuation]
try: Added retry budget to the upload path so that flaky networks recover
warning: subject opens with 'added'; conventional commits use the imperative [imperative-mood]
try: add retry budget to the upload path so that flaky networks recover.
Why these rules
The rule set is not a style opinion. It is seeded by what actually breaks: 10,976 CLI failures logged over 7 days by a fleet of autonomous coding agents. Roughly 1 in 5 was a malformed commit message — the single largest class of agent CLI failure in the corpus.
| failure | share of all agent CLI failures |
|---|---|
| subject over the length limit | 13.8% |
missing tag(scope): format |
8.1% |
The useful detail is the shape of the overshoot, not its existence:
| how far over the 72-char limit | share of overshoots |
|---|---|
| 1–5 chars | 57% |
| 6–10 chars | 24% |
| 11+ chars | 19% |
81% of overshoots miss by 10 characters or fewer. Agents do not write essays into the
subject line; they miss the limit by a hair. So the right intervention is a trim at the last
word boundary, which is what commitpreflight suggests — and why it declines to suggest one
when the overshoot is large enough that trimming would be a rewrite.
Use it as a git hook
printf '#!/bin/sh\nexec commitpreflight "$1"\n' > .git/hooks/commit-msg
chmod +x .git/hooks/commit-msg
Exit code is 1 when any error-severity finding fires, 0 otherwise. --strict makes
warnings blocking too.
Use it from an agent
--json gives a machine-readable list your agent can act on without parsing prose:
$ commitpreflight --json .git/COMMIT_EDITMSG
[
{
"rule": "imperative-mood",
"severity": "warning",
"message": "subject opens with 'added'; conventional commits use the imperative",
"fix": "add retry budget to the upload path"
}
]
Or call it directly:
from commitpreflight import check
for finding in check("feat(api): Added a thing."):
print(finding.rule, finding.severity, finding.fix)
Rules
| rule | severity | what it catches |
|---|---|---|
empty |
error | no message at all |
conventional-format |
error | header is not tag(scope): subject |
tag-case |
error | tag is not lowercase |
tag-unknown |
error | tag outside the allowed set |
scope-required |
error | scope missing when required |
scope-empty |
error | tag(): subject |
subject-empty |
error | nothing after the tag |
subject-length |
error | header over the limit |
subject-punctuation |
error | subject ends with a period |
body-blank-line |
error | body not separated from the subject |
imperative-mood |
warning | added / fixing / updates openers |
subject-case |
warning | subject starts capitalised |
body-line-length |
warning | wrappable body line over the limit |
Configuration
Optional, in pyproject.toml:
[tool.commitpreflight]
max_subject = 72
max_body_line = 100
require_conventional = true
require_scope = false
allow_unknown_tags = false
tags = ["feat", "fix", "docs", "refactor", "test", "chore"]
disable = ["subject-case"]
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file commitpreflight-0.1.0.tar.gz.
File metadata
- Download URL: commitpreflight-0.1.0.tar.gz
- Upload date:
- Size: 7.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
32a3223259248e5a9550b13a3ab7e93b7412708d880829068e66c8cca2b81bb6
|
|
| MD5 |
86a5cd7736bd676ef78429abc9bba1ba
|
|
| BLAKE2b-256 |
3f556c0639c37cf8c41a9977ae59c86d7090b3df99a4c6cd1c340b18ad9784e9
|
File details
Details for the file commitpreflight-0.1.0-py3-none-any.whl.
File metadata
- Download URL: commitpreflight-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ecf8d45fbf0d1740be38e0547aa9bf0edc53b5ea98d094c9547d5ee885e74d8
|
|
| MD5 |
111ca08aa5c0c077337b5c0515ba5adb
|
|
| BLAKE2b-256 |
ed0e5f9933c31d0ee6b37806f57978f7db7415be79324e588cf08dab6d47508f
|