Commit Check is commit policy as code for teams and AI agents. It validates
commit messages, branch names, authors, sign-offs, tags, committed files and AI
attribution against one cchk.toml β in your commit hook, in CI, on every
pull request and inside your AI agent. When the fix is obvious, it hands you
the line.
On GitHub? Install the Commit Check App and every push and pull request gets a check run, with no workflow file. It is free for public repositories and personal accounts.
pip install commit-check # Python 3.10+
commit-check --message --branchNo config needed: out of the box it checks Conventional Commits and Conventional Branch.
Stop bad commits before they exist. In .pre-commit-config.yaml:
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.4
hooks:
- id: check-message
- id: check-branchAdd args: [--fix] to check-message and the hook corrects mechanical slips
itself β a type's case, a missing colon β instead of rejecting the commit.
The hooks for the other checks (check-author-name, check-author-email,
check-no-force-push, check-tag, check-files) are listed in the
pre-commit guide.
The same cchk.toml drives all of them:
| Run it in | How |
|---|---|
| GitHub, no workflow | Install the GitHub App β one check run per commit |
| GitHub Actions | uses: commit-check/commit-check-action@v2 β see commit-check-action |
| AI agents | --format json, the Python API, or the MCP server |
| Check | Flag | Rules |
|---|---|---|
| Commit message β Conventional Commits, subject length, case and mood, WIP and fixup commits, body, sign-off | --message |
CC001βCC012 |
AI attribution β forbid it, or require an Assisted-by: disclosure |
--message |
CC013βCC016 |
| Author name and email | --author-name --author-email |
CC101βCC102 |
| Branch name β Conventional Branch, rebased onto a target | --branch |
CC201βCC202 |
| Force push | --no-force-push |
CC301 |
| Committed files β size, forbidden paths, path length | --files |
CC302βCC304 |
| Tag names β SemVer or your own pattern | --tag |
CC401 |
Every rule, with examples: commit-check.com/rules
Put a cchk.toml (or commit-check.toml) in the repository root or in .github/:
[commit]
subject_max_length = 72
require_signed_off_by = true
ai_attribution = "disclose"
ignore_authors = ["dependabot[bot]", "renovate[bot]"]
[branch]
conventional_branch = true- Roll out gently:
warn = ["CC003"]reports a rule in full without failing the run. - One policy per organization:
inherit_from = "github:my-org/.github:cchk.toml"β see organization config. - Override anywhere: every key has a CLI flag and a
CCHK_*environment variable. - Editor support: the schema is on SchemaStore, so VS Code and JetBrains autocomplete
cchk.toml.
Configuration reference β every key, its default, CLI flag and environment variable
Priority: CLI > env > TOML > default. Booleans accept true/false, yes/no,
1/0; lists are comma-separated on the CLI and in env vars.
| Key | Default | CLI flag | Env var | Meaning |
|---|---|---|---|---|
warn (top level) |
[] |
β | β | Checks or rule IDs to report without failing the run (see "Report a rule without enforcing it") |
commit.conventional_commits |
true |
--conventional-commits |
CCHK_CONVENTIONAL_COMMITS |
Enforce the Conventional Commits format (CC001) |
commit.message_pattern |
"" |
β | CCHK_MESSAGE_PATTERN |
Custom regex the whole message must match; when set it replaces the Conventional Commits check (still reported as CC001) |
commit.subject_capitalized |
false |
--subject-capitalized |
CCHK_SUBJECT_CAPITALIZED |
Require the subject to start with a capital letter (CC002) |
commit.subject_imperative |
false |
--subject-imperative |
CCHK_SUBJECT_IMPERATIVE |
Require the subject to use the imperative mood (CC003) |
commit.subject_max_length |
80 |
--subject-max-length |
CCHK_SUBJECT_MAX_LENGTH |
Maximum subject length (CC004) |
commit.subject_min_length |
5 |
--subject-min-length |
CCHK_SUBJECT_MIN_LENGTH |
Minimum subject length (CC005) |
commit.allow_commit_types |
feat, fix, docs, style, refactor, test, chore, perf, build, ci |
--allow-commit-types |
CCHK_ALLOW_COMMIT_TYPES |
Allowed <type> values in the subject |
commit.allow_merge_commits |
true |
--allow-merge-commits |
CCHK_ALLOW_MERGE_COMMITS |
Allow merge commits (CC006) |
commit.allow_revert_commits |
true |
--allow-revert-commits |
CCHK_ALLOW_REVERT_COMMITS |
Allow revert commits (CC007) |
commit.allow_empty_commits |
true |
--allow-empty-commits |
CCHK_ALLOW_EMPTY_COMMITS |
Allow empty commit messages (CC008) |
commit.allow_fixup_commits |
true |
--allow-fixup-commits |
CCHK_ALLOW_FIXUP_COMMITS |
Allow fixup!, squash! and amend! commits (CC009) |
commit.allow_wip_commits |
true |
--allow-wip-commits |
CCHK_ALLOW_WIP_COMMITS |
Allow WIP commits (CC010) |
commit.require_body |
false |
--require-body |
CCHK_REQUIRE_BODY |
Require a commit body (CC011) |
commit.require_signed_off_by |
false |
--require-signed-off-by |
CCHK_REQUIRE_SIGNED_OFF_BY |
Require a Signed-off-by: trailer (CC012) |
commit.ignore_authors |
[] |
--ignore-authors |
CCHK_IGNORE_AUTHORS |
Authors and co-authors whose commits skip the commit checks |
commit.ai_attribution |
"ignore" |
--ai-attribution |
CCHK_AI_ATTRIBUTION |
ignore, forbid or disclose. forbid rejects commits carrying known AI tool signatures (CC013); disclose accepts AI assistance disclosed with one of ai_disclosure_trailers (CC014) and rejects the tool as a co-author (CC015) or as a sign-off (CC016) |
commit.ai_disclosure_trailers |
Assisted-by, Generated-by |
--ai-disclosure-trailers |
CCHK_AI_DISCLOSURE_TRAILERS |
Trailers that disclose AI assistance under disclose; the first is the one a fix is written with. List Co-authored-by to accept the tool as a co-author |
commit.ai_disclosure_pattern |
"" |
--ai-disclosure-pattern |
CCHK_AI_DISCLOSURE_PATTERN |
Regex the disclosure's value must match under disclose, e.g. ^\S+/\S+ for agent/model; empty accepts any value, but a trailer with no value at all is always reported |
commit.author_email_pattern |
"^.+@.+$" |
--author-email-pattern |
CCHK_AUTHOR_EMAIL_PATTERN |
Regex the author email must match (CC102, with --author-email) |
commit.author_name_pattern |
"" |
--author-name-pattern |
CCHK_AUTHOR_NAME_PATTERN |
Regex the author name must match (CC101, with --author-name) |
branch.conventional_branch |
true |
--conventional-branch |
CCHK_CONVENTIONAL_BRANCH |
Enforce <type>/<description> branch names (CC201) |
branch.require_description_grammar |
false |
--require-description-grammar |
CCHK_REQUIRE_DESCRIPTION_GRAMMAR |
Also hold the description after <type>/ to the Conventional Branch grammar: lowercase alphanumeric words joined by single hyphens, dots allowed inside a word (CC201). Off by default, since bot branches such as dependabot/npm_and_yarn/lodash-4.17.21 do not follow it |
branch.allow_branch_types |
feature, bugfix, hotfix, release, chore, feat, fix, build, ci, docs, perf, refactor, test, style, ai, claude, codex, copilot, cursor, agents, dependabot, renovate |
--allow-branch-types |
CCHK_ALLOW_BRANCH_TYPES |
Allowed branch <type> prefixes; each entry is a regex matched against the whole type |
branch.allow_branch_names |
[] |
--allow-branch-names |
CCHK_ALLOW_BRANCH_NAMES |
Extra branch names allowed besides main, master, develop, HEAD, PR-.+; each entry is a regex matched against the whole branch name, so create-pull-request/.+ allows a family of them |
branch.require_rebase_target |
"" |
--require-rebase-target |
CCHK_REQUIRE_REBASE_TARGET |
Branch the current branch must be rebased onto (CC202); empty disables |
branch.ignore_authors |
[] |
--branch-ignore-authors |
CCHK_BRANCH_IGNORE_AUTHORS |
Authors whose branches skip the branch checks |
push.allow_force_push |
true |
β (--no-force-push runs the check) |
CCHK_ALLOW_FORCE_PUSH |
Reserved; has no effect today. CC301 runs only with --no-force-push, which always rejects a non-fast-forward push |
tag.regex |
SemVer with optional v |
--tag-regex |
CCHK_TAG_REGEX |
Pattern tag names must match (CC401); empty disables |
files.max_size |
"" (off) |
--files-max-size |
CCHK_FILES_MAX_SIZE |
Largest committed file, in bytes or with KB/MB/GB (CC302) |
files.prohibited_patterns |
[] |
--files-prohibited-patterns |
CCHK_FILES_PROHIBITED_PATTERNS |
fnmatch patterns committed paths must not match (CC303) |
files.max_path_length |
0 (off) |
--files-max-path-length |
CCHK_FILES_MAX_PATH_LENGTH |
Longest allowed committed path, in characters (CC304) |
Output and appearance are per-run flags, not configuration: --format json,
--no-banner, --compact, --dry-run, --rev, --config. Color follows
NO_COLOR / FORCE_COLOR. Long-form descriptions: commit-check.com/configuration
--format json gives each check a stable rule ID, the value it saw and a
suggestion β plus the corrected value itself when the correction is
mechanical, so an agent can apply it without interpreting anything:
echo "Fix: add streaming support" | commit-check -m --format jsonThe failing entry of its checks array:
{
"rule_id": "CC001",
"check": "message",
"status": "fail",
"value": "Fix: add streaming support",
"error": "The commit message should follow Conventional Commits. See https://www.conventionalcommits.org",
"suggest": "Use \"fix: add streaming support\"",
"fix": "fix: add streaming support",
"docs_url": "https://commit-check.com/rules/#cc001",
"reason": ""
}A check that did not run has status skip, and reason says why when the
author is on an ignore_authors list: "author dependabot[bot] is in [commit].ignore_authors".
The same result, without a subprocess:
from commit_check.api import validate_message
validate_message("feat: add streaming support")["status"] # "pass"commit_check.api also has validate_branch, validate_author,
validate_tag, validate_push and validate_all, all returning the same
shape. Unlike the CLI they do not read cchk.toml: pass the policy as a
config dict. The Python API
page covers each function and how to load a repository's file.
Exit codes: 0 passed Β· 1 a check failed Β· 2 nothing was checked β bad
usage, or a config that is missing, is not valid TOML, names an unknown rule,
holds a regex that does not compile or gives a [commit] or [branch] setting
a value of the wrong type (subject_max_length = "abc"). A [files] value it
cannot use turns that rule off with a notice instead. --dry-run reports everything and exits 0. More output formats: examples.
| Commit Check | commitlint | GitHub Rulesets | |
|---|---|---|---|
| Conventional Commits | β | β | regex only |
| Branch, tag, author and file rules | β | β | β * |
| Feedback before the push | β | β | β |
| Works with zero config | β | β | β |
| No Node.js | β | β | β |
| JSON output, Python API, MCP | β | β | β |
*Push rulesets need a Team or Enterprise plan for private repositories. The full comparison, including YACC and custom hooks: commit-check.com/compare
[](https://commit-check.com)
Questions and ideas go to Discussions, bugs to Issues. Releases follow Semantic Versioning. Released under the MIT License.
