- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .claude | ||
| .github | ||
| assets | ||
| commit_check | ||
| debian | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .pre-commit-hooks.yaml | ||
| AGENTS.md | ||
| cchk.toml | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| noxfile.py | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
Commit Check
Table of Contents
- Overview
- Quick Start
- Installation
- Configuration
- AI-Native Usage
- Examples
- Badging your repository
- Why Commit Check?
- Versioning
- Have question or feedback?
- License
Overview
Commit Check is a lightweight policy engine for Git commit metadata.
It validates commit messages, branch names, author identity, signoff trailers, AI attribution policy, and push safety — using one versioned TOML policy across local hooks, CI, GitHub Actions, the hosted GitHub App, and AI automation.
- One policy file:
cchk.toml - Multiple enforcement points: CLI, pre-commit, CI / GitHub Actions, or the Commit Check GitHub App with no workflow file
- Machine-readable output: JSON + Python API for automation and AI agents
Quick Start
1. Install and run with zero configuration:
pip install commit-check
commit-check --message --branch
2. Add to your pre-commit hooks (.pre-commit-config.yaml):
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.17.0
hooks:
- id: check-message
- id: check-branch
3. Add a badge to your repository:
[](https://github.com/commit-check/commit-check)
Installation
To install Commit Check, you can use pip:
pip install commit-check
Or install directly from the GitHub repository:
pip install git+https://github.com/commit-check/commit-check.git@main
Then, run commit-check --help or cchk --help (alias for commit-check) from the command line.
For more information, see the docs.
Configuration
Commit Check can be configured in three ways (in order of priority):
- Command-line arguments — Override settings for specific runs
- Environment variables — Configure via
CCHK_*environment variables - Configuration files — Use
cchk.tomlorcommit-check.toml
Use Default Configuration
-
Commit Check uses a default configuration if you do not provide a
cchk.tomlorcommit-check.tomlfile. -
The default configuration is lenient — it only checks whether commit messages follow the Conventional Commits specification and branch names follow the Conventional Branch convention.
Use Custom Configuration File
To customize the behavior, create a configuration file named cchk.toml or commit-check.toml in your repository's root directory or in the .github folder, e.g., cchk.toml or .github/cchk.toml.
# Rules to report without enforcing: they print in full, never fail the run.
# Name a check or its rule ID. See "Report a rule without enforcing it" below.
warn = ["branch"]
[commit]
# https://www.conventionalcommits.org
conventional_commits = true
subject_imperative = true
subject_max_length = 80
allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "ci"]
allow_merge_commits = true
allow_wip_commits = false
require_signed_off_by = false
# Bypass checks for bot/automation authors and co-authors:
ignore_authors = ["dependabot[bot]", "renovate[bot]", "copilot[bot]"]
# AI attribution policy: "ignore" (default) or "forbid"
# "forbid" rejects commits with known AI tool signatures
ai_attribution = "forbid"
[branch]
# https://conventionalbranch.org
conventional_branch = true
# Optional: the defaults are a superset of the Conventional Branch spec — spec
# types plus Conventional Commit types (build, ci, docs, perf, refactor, style,
# test) and AI/bot prefixes (ai, claude, codex, copilot, cursor, dependabot,
# renovate), see https://commit-check.com/configuration/. Omit this option to use the defaults.
allow_branch_types = [
"feature",
"bugfix",
"hotfix",
"release",
"chore",
"feat",
"fix",
"build",
"ci",
"docs",
"perf",
"refactor",
"style",
"test",
]
Tip
IDE Autocompletion
commit-check's TOML schema is published on SchemaStore, so editors like VS Code (via Even Better TOML), PyCharm, and IntelliJ provide autocompletion, validation, and documentation tooltips for
cchk.tomlout of the box — no manual schema path configuration needed.
Report a rule without enforcing it
A rule is normally on or off. warn gives it a third setting: run, report
the finding in full, and never fail the run. Name a check or its rule ID:
warn = ["branch", "CC003"]
A warned rule prints the same block as a failure with warning in place of
failed, no rejection banner, and one closing line saying the run is not
failed by it; with --compact it is one [WARN] line. The exit code counts
only enforced rules, and in --format json the check's status is warn, the
top-level status stays pass, and warnings counts them. This is how a team
adopts a rule gradually: turn it on
as a warning, watch what it catches, then drop it from warn when the history
is clean. A name that matches no rule is a configuration error, so a typo
cannot leave a rule silently enforced.
Organization-Level Configuration (inherit_from)
Share a base configuration across all repositories in your organization using inherit_from:
# .github/cchk.toml — inherits from org-level config, then overrides locally
inherit_from = "github:my-org/.github:cchk.toml"
[commit]
subject_max_length = 72 # Local override
The inherit_from field accepts:
- A GitHub shorthand (recommended):
inherit_from = "github:owner/repo:path/to/cchk.toml" - A GitHub shorthand with ref:
inherit_from = "github:owner/repo@main:path/to/cchk.toml" - A local file path (relative or absolute):
inherit_from = "../shared/cchk.toml" - An HTTPS URL:
inherit_from = "https://example.com/cchk.toml"
The github: shorthand fetches from raw.githubusercontent.com. HTTP (non-TLS) URLs are rejected for security.
Local settings always override the inherited base configuration.
Use CLI Arguments or Environment Variables
For one-off checks or CI/CD pipelines, you can configure via CLI arguments or environment variables:
# Using CLI arguments
commit-check --message --subject-imperative=true --subject-max-length=72
# Using environment variables
export CCHK_SUBJECT_IMPERATIVE=true
export CCHK_SUBJECT_MAX_LENGTH=72
commit-check --message
# In pre-commit hooks (.pre-commit-config.yaml)
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.17.0
hooks:
- id: check-message
args:
- --subject-imperative=false
- --subject-max-length=100
- id: check-author-email
args:
- --no-banner
- --author-email
- --author-email-pattern=^.+@example\.com$
See the Configuration documentation for all available options.
Check Push Safety
Use --no-force-push in a pre-push hook to inspect the ref updates Git
provides on stdin, or run it directly to compare HEAD with the current
branch's configured upstream:
# Standalone preflight check against the current branch's upstream
commit-check --no-force-push
# In pre-commit hooks (.pre-commit-config.yaml)
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.17.0
hooks:
- id: check-no-force-push
stages: [pre-push]
Note
Piping
git pushintocommit-checkis not a prevention mechanism. The push has already been started, and standardgit pushoutput does not carry the pre-push ref metadata thatcommit-checkuses.
Check Tag Names
Use --tag to validate the name of every tag pointing at HEAD (or at
--rev). The default pattern accepts SemVer with an optional leading v
(v1.2.3 or 1.2.3, pre-release and build suffixes included); set
regex in the [tag] config section or pass --tag-regex to change it.
A commit with no tag is reported as skipped, not failed.
# Validate the tag(s) at HEAD, e.g. in a CI job triggered by a tag push
commit-check --tag
# Enforce a custom scheme
commit-check --tag --tag-regex '^v\d+\.\d+\.\d+$'
# In pre-commit hooks (.pre-commit-config.yaml): validates the tag names
# a push carries, from the pre-push ref metadata on stdin
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.17.0
hooks:
- id: check-tag
stages: [pre-push]
Check Committed Files
Use --files to police metadata about the files a commit touches — never
their contents. Three independent, opt-in limits live in the [files]
config section: a size cap, prohibited path patterns, and a path-length
cap. GitHub's own push rules do this only on Team and Enterprise plans;
here it works on every plan and every forge.
[files]
# Reject files larger than this (bytes, or with a KB/MB/GB suffix)
max_size = "5MB"
# Reject paths matching any fnmatch pattern; a bare pattern like *.pem
# also matches the file name at any depth
# Patterns are case-sensitive on every platform, like git pathspecs
prohibited_patterns = ["*.pem", "*.key", ".env", "id_rsa*"]
# Reject paths longer than this many characters
max_path_length = 250
# Validate the commit at HEAD, or any commit via --rev
commit-check --files
commit-check --files --rev abc1234
# In pre-commit hooks (.pre-commit-config.yaml): a native git pre-push hook
# feeds the pushed refs on stdin, and every commit the push adds is validated
# (a tag on already-pushed history adds nothing, so it is skipped)
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.17.0
hooks:
- id: check-files
stages: [pre-push]
A commit that only deletes files is reported as skipped — removing a file adds nothing to police. Content scanning (entropy, token detection) is deliberately out of scope: pair these checks with a scanner like gitleaks if you need it.
AI-Native Usage
Commit Check is designed to be consumed by AI agents, LLM toolchains, and automation scripts — not just by humans reading terminal output.
Machine-Readable JSON Output (--format json)
Pass --format json to any CLI invocation to receive structured JSON instead
of human-readable ASCII art. The exit code is unchanged (0 = pass, 1 = fail),
so existing CI scripts continue to work:
echo "feat: add streaming support" | commit-check -m --format json
{
"status": "pass",
"warnings": 0,
"checks": [
{
"rule_id": "CC001",
"check": "message",
"status": "pass",
"value": "feat: add streaming support",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc001"
},
{
"rule_id": "CC004",
"check": "subject_max_length",
"status": "pass",
"value": "feat: add streaming support",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc004"
},
{
"rule_id": "CC005",
"check": "subject_min_length",
"status": "pass",
"value": "feat: add streaming support",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc005"
}
]
}
On failure the failing checks carry the full error and suggest fields
an agent needs to self-correct:
echo "wip bad commit" | commit-check -m --format json
{
"status": "fail",
"warnings": 0,
"checks": [
{
"rule_id": "CC001",
"check": "message",
"status": "fail",
"value": "wip bad commit",
"error": "The commit message should follow Conventional Commits. See https://www.conventionalcommits.org",
"suggest": "Use <type>(<scope>): <description>, where <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, build, ci",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc001"
},
{
"rule_id": "CC004",
"check": "subject_max_length",
"status": "pass",
"value": "wip bad commit",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc004"
},
{
"rule_id": "CC005",
"check": "subject_min_length",
"status": "pass",
"value": "wip bad commit",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc005"
}
]
}
When the correction is mechanical, fix carries the corrected value and
suggest names it, so an agent (or a person) can apply it without
interpreting anything: a type written Fix or misspelt feta, a missing
colon, a lowercase description under subject_capitalized, a WIP: marker,
a missing Signed-off-by trailer, a branch typed Feature/x, or AI
attribution lines under ai_attribution = "forbid". Anything that takes a
judgment, such as choosing a type for a bare subject or shortening a long one,
leaves fix empty and suggest generic.
echo "Fix: add streaming support" | commit-check -m --format json
{
"status": "fail",
"warnings": 0,
"checks": [
{
"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"
}
]
}
(The passing checks are omitted from this example.)
Quieter Human-Readable Output
For terminal workflows that still want plain text, commit-check now supports two lower-noise output modes:
--no-bannerkeeps the normal failure details and suggestions, but removes the ASCII-art failure banner.--compactemits a single[FAIL]line per failing check and implies--no-banner.
echo "wip bad commit" | commit-check -m --no-banner
CC001 message check failed ==> wip bad commit
The commit message should follow Conventional Commits. See https://www.conventionalcommits.org
Suggest: Use <type>(<scope>): <description>, where <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, build, ci
Docs: https://commit-check.com/rules/#cc001
echo "wip bad commit" | commit-check -m --compact
[FAIL] CC001 message: wip bad commit
Python API (no subprocess required)
The commit_check.api module exposes a lightweight, import-friendly interface
so AI agents, tools, and scripts can validate commits without spawning a
subprocess. All functions return plain dicts that are easy to serialise,
forward to an LLM, or chain into larger workflows:
from commit_check.api import validate_message, validate_branch, validate_all
# --- validate a single commit message ---
result = validate_message("feat: add streaming support")
print(result["status"]) # "pass"
# --- validate a branch name ---
result = validate_branch("feature/add-streaming")
print(result["status"]) # "pass"
# --- run multiple checks at once ---
result = validate_all(
message="feat: implement new feature",
branch="feature/new-feature",
author_name="Ada Lovelace",
author_email="ada@example.com",
)
if result["status"] == "fail":
for check in result["checks"]:
if check["status"] == "fail":
print(f"[{check['check']}] {check['error']}")
print(f" suggestion: {check['suggest']}")
# --- supply a custom config to restrict allowed types ---
result = validate_message(
"docs: update readme",
config={"commit": {"allow_commit_types": ["feat", "fix"]}},
)
print(result["status"]) # "fail" — 'docs' not in allowed types
Return-value schema (all API functions):
{
"status": "pass" | "fail" | "skip",
"warnings": <number of checks with status "warn">,
"checks": [
{
"rule_id": "<rule identifier, e.g. CC001>",
"check": "<rule name>",
"status": "pass" | "fail" | "warn" | "skip",
"value": "<actual value that was checked>",
"error": "<human-readable error description>",
"suggest": "<how to fix>",
"fix": "<the corrected value, when it is unambiguous; else empty>",
"docs_url": "<link to the rule's documentation>",
},
# ... one entry per active rule
]
}
warn means the rule was not satisfied but is listed under warn in the
config: the finding is reported and does not fail the run, and warnings
counts these. skip means the rule never ran — the author matched
ignore_authors, or there was nothing to check. It is deliberately not pass: a skipped rule
validated nothing, so reporting it as a pass makes a bypassed policy
indistinguishable from an enforced one. A skipped check carries no value,
since nothing was examined.
The top-level status is skip only when every check skipped; one real
verdict makes it pass or fail as before. Only fail is an error, and the
CLI exit code follows that — a fully skipped run still exits 0, so code
branching on status == "fail" is unaffected.
echo "chore(deps): bump commit-check" | CCHK_IGNORE_AUTHORS="dependabot[bot]" commit-check -m --format json
{
"status": "skip",
"warnings": 0,
"checks": [
{
"rule_id": "CC001",
"check": "message",
"status": "skip",
"value": "",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc001"
},
{
"rule_id": "CC004",
"check": "subject_max_length",
"status": "skip",
"value": "",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc004"
},
{
"rule_id": "CC005",
"check": "subject_min_length",
"status": "skip",
"value": "",
"error": "",
"suggest": "",
"fix": "",
"docs_url": "https://commit-check.com/rules/#cc005"
}
]
}
Available API functions:
validate_message(message, *, config=None)— validate a commit message stringvalidate_branch(branch=None, *, config=None)— validate a branch name (defaults to current git branch)validate_author(name=None, email=None, *, config=None)— validate author name/emailvalidate_all(message, branch, author_name, author_email, *, config=None)— run all checks at once
For detailed usage instructions including pre-commit hooks, CLI commands, and STDIN examples, see the Usage Examples documentation.
Examples
Check Commit Message Failed
Commit rejected by Commit-Check.
(c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c)
/ ._. \ / ._. \ / ._. \ / ._. \ / ._. \
__\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__
(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
|| E || || R || || R || || O || || R ||
_.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._
(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)
`-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´
Commit rejected.
CC001 message check failed ==> test commit message check
The commit message should follow Conventional Commits. See https://www.conventionalcommits.org
Suggest: Use <type>(<scope>): <description>, where <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, build, ci
Docs: https://commit-check.com/rules/#cc001
Check Branch Naming Failed
Commit rejected by Commit-Check.
(c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c) (c).-.(c)
/ ._. \ / ._. \ / ._. \ / ._. \ / ._. \
__\( C )/__ __\( H )/__ __\( E )/__ __\( C )/__ __\( K )/__
(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)(_.-/'-'\-._)
|| E || || R || || R || || O || || R ||
_.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._ _.' '-' '._
(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)(.-./`-´\.-.)
`-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´ `-´
Commit rejected.
CC201 branch check failed ==> test-branch
The branch should follow Conventional Branch. See https://conventionalbranch.org
Suggest: Use <type>/<description> with allowed types or add branch name to allow_branch_names in config, or use ignore_authors in config branch section to bypass
Docs: https://commit-check.com/rules/#cc201
For more examples, see the example documentation.
Badging your repository
You can add a badge to your repository to show that you use commit-check!
Markdown
[](https://github.com/commit-check/commit-check)
reStructuredText
.. image:: https://img.shields.io/badge/commit--check-enabled-brightgreen?logo=Git&logoColor=white&color=%232c9ccd
:target: https://github.com/commit-check/commit-check
:alt: commit-check
Why Commit Check?
The table below compares common approaches to commit policy enforcement.
commitlint is a specialized commit-message linter. GitHub Rulesets
are platform-native server-side enforcement. Custom Git hooks and the
pre-commit framework are integration mechanisms, so the last column
reflects a DIY approach rather than built-in product features.
| Feature | Commit Check | commitlint | YACC1 | GitHub Rulesets | Custom hooks |
|---|---|---|---|---|---|
| Conventional Commits enforcement | ✅ | ✅ | Partial | Partial2 | DIY |
| Branch naming validation | ✅ | ❌ | ✅ | ✅2 | DIY |
| Tag naming validation | ✅ | ❌ | ❌ | ✅2 | DIY |
| File size / path restrictions | ✅ | ❌ | ❌ | ✅3 | DIY |
| Force push blocking | ✅ | ❌ | ❌ | ✅ | DIY |
| Author name / email validation | ✅ | ❌ | ✅ | ✅2 | DIY |
| Signed-off-by trailer enforcement | ✅ | Partial4 | ❌ | ❌ | DIY |
| Co-author ignore list | ✅ | ❌ | Partial5 | ❌ | DIY |
| Organization-level shared config | ✅ | ✅ | ✅ | ✅ | DIY |
| Zero-config defaults | ✅ | ❌ | ❌ | ❌ | ❌ |
| Works without Node.js | ✅ | ❌ | ✅ | ✅ | Depends |
| Native TOML configuration | ✅ | ❌ | ❌ | ❌ | Depends |
| Git hook / pre-commit integration | ✅ | Partial | ❌ | ❌ | ✅ |
| CI/CD-friendly configuration | ✅ | Partial | ❌ | ❌ | DIY |
| Open source & free | ✅ | ✅ | ❌ | ❌3 | ✅ |
| Client-side (pre-commit) enforcement | ✅ | ✅ | ❌ | ❌ | ✅ |
| AI-native (JSON API + Python SDK) | ✅ | ❌ | ❌ | ❌ | ❌ |
For commitlint, organization-level shared config is typically delivered via
shareable config packages or local files.
For YACC (Yet Another Commit Checker), conventional commit enforcement
is regex-based rather than Conventional Commits-aware; author validation
verifies committer name/email against Bitbucket user accounts or custom regex;
the plugin supports global → project → repository config inheritance;
it is a server-side pre-receive hook and merge check (no client-side
pre-commit), is paid (per-user licensing), and runs on Java (no Node.js needed).
For GitHub Rulesets, push rulesets enforce metadata via regex patterns — they can match branch/tag names, commit messages, and author email, but have no awareness of Conventional Commits semantics (types, scopes, breaking-change markers). They apply server-side and require a GitHub plan (Free for public repos, Team/Enterprise for private/internal repos with push rulesets). They are not portable to other Git platforms and do not provide local pre-commit feedback.
DIY means you can implement a
capability with custom Git hooks or pre-commit scripts, but it is not
provided as a turnkey policy layer.
Versioning
Versioning follows Semantic Versioning.
Have question or feedback?
Please post to issues or start a discussion for feedback, feature requests, or bug reports.
License
This project is released under the MIT License.
-
Yet Another Commit Checker is a paid Bitbucket Server / Data Center plugin (server-side pre-receive hook and merge check). ↩︎
-
GitHub Rulesets enforce these via regex patterns in push rulesets (metadata restrictions). They are regex-based and do not understand Conventional Commits or Conventional Branch semantics. ↩︎
-
GitHub Rulesets require a GitHub plan. Push rulesets (metadata restrictions) require Team or Enterprise plans for private/internal repos; branch/tag rulesets are available on Free plans for public repos. ↩︎
-
commitlintprovides a communitysigned-off-byrule (@commitlint/rule-signed-off-by) that must be installed and configured separately; it is not part of the default@commitlint/config-conventionalpreset. ↩︎ -
YACC can exclude commits from specific Bitbucket users, user groups, or service users (bots), but does not parse
Co-authored-by:trailers in commit messages. ↩︎
