Configuration¶
Commit Check reads its settings from four places. When the same option is set in more than one, the first one listed wins:
- Command-line arguments —
--subject-imperative=true - Environment variables —
CCHK_SUBJECT_IMPERATIVE=true - A configuration file —
cchk.tomlorcommit-check.toml - Built-in defaults
That ordering is what makes the layering useful: the file carries the policy the project agreed on, the environment overrides it for a single CI job, and a flag overrides both for a single run.
Defaults are not "nothing"
Two different things decide whether a rule fires: whether you asked for
that check at all, and what the option defaults to. A check only runs when
its flag is passed — --message never evaluates branch rules — but once a
check is running, these apply with no configuration file present:
| Check | Enforced by default |
|---|---|
--message |
Conventional Commits (CC001), subject lengths of 5–80 characters (CC004, CC005), and an allow-list of ten commit types |
--branch |
Conventional Branch (CC201) and an allow-list of twenty-one branch types |
--author-name / --author-email |
The built-in name and email patterns (CC101, CC102) |
Off until you turn them on: subject capitalization, imperative mood, required body and signoff, and rebase requirements.
The allow_* options are a mix, so read them individually rather than
assuming: allow_commit_types and allow_branch_types are allow-lists
that restrict from the start, while allow_merge_commits,
allow_revert_commits, allow_empty_commits, allow_fixup_commits,
allow_wip_commits and allow_force_push all default to permitting
everything. The Default column in the
rules reference is the full picture.
Where the config file lives¶
The file is TOML, and may be called cchk.toml or commit-check.toml. Commit
Check searches four locations and uses the first that exists:
cchk.tomlcommit-check.toml.github/cchk.toml.github/commit-check.toml
Pass --config to point at one directly and skip the search:
Why .github/
Putting the file in .github/ keeps the repository root uncluttered and
matches where Dependabot and Renovate already keep theirs. Commit Check
treats both locations identically.
Editor autocompletion
The TOML schema is published on SchemaStore,
so VS Code (via
Even Better TOML),
PyCharm and IntelliJ offer completion, validation and inline documentation
for cchk.toml with nothing to configure.
Inheriting a shared config¶
An organization can keep one base policy and have every repository build on it.
Point inherit_from at the shared file; the parent loads first, and anything
set locally overrides it. The key itself is never passed to the validation
engine.
inherit_from = "github:my-org/.github:cchk.toml"
[commit]
subject_max_length = 72 # overrides whatever the parent set
The value can be a GitHub shorthand, a local path or an HTTPS URL:
| Form | Example |
|---|---|
| GitHub, default branch | github:owner/repo:path/to/cchk.toml |
| GitHub, pinned branch | github:owner/repo@main:path/to/cchk.toml |
| Local file | ../../shared/org-cchk.toml |
| HTTPS URL | https://example.com/shared/cchk.toml |
Inheritance fails quietly
If the target is unreachable or the format is not recognized, Commit Check ignores the inheritance and uses the local configuration alone. Plain HTTP URLs are rejected outright. A repository that silently stops inheriting still passes its own checks, so pin the branch when the policy matters.
A worked example¶
Every line below that differs from the built-in default is marked, so it is clear what this file is actually changing:
[commit]
# https://www.conventionalcommits.org
conventional_commits = true
# message_pattern = "" # optional: a custom regex, replacing the above
subject_capitalized = false
subject_imperative = true # changed: off by default
subject_max_length = 80
subject_min_length = 5
# changed: a subset of the default list, which also has perf, build and ci
allow_commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore"]
allow_merge_commits = true
allow_revert_commits = true
allow_empty_commits = false # changed: allowed by default
allow_fixup_commits = true
allow_wip_commits = false # changed: allowed by default
require_body = false
require_signed_off_by = false
ai_attribution = "forbid" # changed: "ignore" by default
# ignore_authors = [] # optional: bypass all commit checks for these authors
[push]
allow_force_push = true # set false to block force pushes
[branch]
# https://conventionalbranch.org
conventional_branch = true
# changed: spec types only. The default is a superset — these plus the
# Conventional Commit types, AI agent prefixes and bot prefixes — so setting
# this at all narrows it. Omit the line to accept all of them.
allow_branch_types = ["feature", "bugfix", "hotfix", "release", "chore"]
# allow_branch_names = [] # optional: extra standalone names, e.g. ["develop"]
# require_rebase_target = "main" # optional: no rebase requirement by default
# ignore_authors = [] # optional: as above, for branch checks
allow_* options describe what is permitted
They read backwards from most linters. allow_wip_commits = false is the
setting that rejects WIP commits; leaving it at its default of true
lets them through.
Command-line arguments¶
Every option can be set as a flag, which is what makes a TOML file optional
entirely — useful when the policy lives in .pre-commit-config.yaml instead.
| Type | Form |
|---|---|
| Boolean | --option-name=true / --option-name=false |
| Integer | --option-name=80 |
| List | --option-name=value1,value2,value3 |
| String | --option-name=value |
$ commit-check --message --subject-imperative=false
$ commit-check --message --subject-max-length=72
$ commit-check --message --allow-commit-types=feat,fix,docs
$ commit-check --branch --allow-branch-types=feature,bugfix,hotfix
Used from a hook definition, with no config file anywhere in the repository:
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.13.1
hooks:
- id: check-message
args:
- --subject-imperative=false
- --subject-max-length=100
- --allow-merge-commits=false
Environment variables¶
Any option can also be set through the environment, which is the practical way
to vary policy per CI job without editing the file. Uppercase the option name,
replace hyphens with underscores, and prefix CCHK_:
$ export CCHK_SUBJECT_MAX_LENGTH=72
$ export CCHK_ALLOW_COMMIT_TYPES=feat,fix,docs,chore
$ CCHK_SUBJECT_MAX_LENGTH=100 commit-check --message
The full mapping between the three forms:
| TOML Config | Environment Variable | CLI Argument |
|---|---|---|
conventional_commits = true |
CCHK_CONVENTIONAL_COMMITS=true |
--conventional-commits=true |
message_pattern = "^PROJ-\\d+: .+" |
CCHK_MESSAGE_PATTERN=^PROJ-\\d+: .+ |
N/A (config file only) |
subject_capitalized = false |
CCHK_SUBJECT_CAPITALIZED=false |
--subject-capitalized=false |
subject_imperative = true |
CCHK_SUBJECT_IMPERATIVE=true |
--subject-imperative=true |
subject_max_length = 80 |
CCHK_SUBJECT_MAX_LENGTH=80 |
--subject-max-length=80 |
subject_min_length = 5 |
CCHK_SUBJECT_MIN_LENGTH=5 |
--subject-min-length=5 |
allow_commit_types = ["feat", "fix"] |
CCHK_ALLOW_COMMIT_TYPES=feat,fix |
--allow-commit-types=feat,fix |
allow_merge_commits = true |
CCHK_ALLOW_MERGE_COMMITS=true |
--allow-merge-commits=true |
allow_revert_commits = true |
CCHK_ALLOW_REVERT_COMMITS=true |
--allow-revert-commits=true |
allow_empty_commits = false |
CCHK_ALLOW_EMPTY_COMMITS=false |
--allow-empty-commits=false |
allow_fixup_commits = true |
CCHK_ALLOW_FIXUP_COMMITS=true |
--allow-fixup-commits=true |
allow_wip_commits = false |
CCHK_ALLOW_WIP_COMMITS=false |
--allow-wip-commits=false |
require_body = false |
CCHK_REQUIRE_BODY=false |
--require-body=false |
require_signed_off_by = false |
CCHK_REQUIRE_SIGNED_OFF_BY=false |
--require-signed-off-by=false |
ignore_authors = ["bot"] |
CCHK_IGNORE_AUTHORS=bot,user |
--ignore-authors=bot,user |
author_email_pattern=^.+@example\.com$ |
CCHK_AUTHOR_EMAIL_PATTERN=^.+@example\.com$ |
--author-email-pattern=^.+@example\.com$ |
author_name_pattern=^.+ .+$ |
CCHK_AUTHOR_NAME_PATTERN=^.+ .+$ |
--author-name-pattern=^.+ .+$ |
conventional_branch = true |
CCHK_CONVENTIONAL_BRANCH=true |
--conventional-branch=true |
allow_branch_types = ["feature"] |
CCHK_ALLOW_BRANCH_TYPES=feature,bugfix |
--allow-branch-types=feature,bugfix |
allow_branch_names = ["develop"] |
CCHK_ALLOW_BRANCH_NAMES=develop,staging |
--allow-branch-names=develop,staging |
require_rebase_target = "main" |
CCHK_REQUIRE_REBASE_TARGET=main |
--require-rebase-target=main |
allow_force_push = true |
CCHK_ALLOW_FORCE_PUSH=true |
--no-force-push (sets allow_force_push to false) |
ai_attribution = "forbid" |
CCHK_AI_ATTRIBUTION=forbid |
--ai-attribution=forbid |
ignore_authors = ["bot"] (in branch section) |
CCHK_BRANCH_IGNORE_AUTHORS=bot,user |
--branch-ignore-authors=bot,user |
Which value wins¶
The four sources layer, so the same option can be set in several at once. Only the highest-priority one takes effect:
$ grep subject_max_length cchk.toml
subject_max_length = 100
$ export CCHK_SUBJECT_MAX_LENGTH=80
$ commit-check --message --subject-max-length=50
The limit applied is 50 — the flag beats the environment, which beats the file. Nothing warns about the values that lost, which is worth remembering when a setting in the file appears to have no effect.
Every option¶
Types are as TOML understands them. A default shown as "" means the option is
unset, which is never the same as the check being off — but it does not mean
the same thing twice, so read the description rather than the cell:
message_patternunset leavesconventional_commitsto generate the pattern. CC001 still runs.author_name_patternunset falls back to the built-in name pattern. CC101 still runs.require_rebase_targetunset is the one case where the check really does not run — there is no branch to compare against.
| Section | Option | Type | Default | Description |
|---|---|---|---|---|
| commit | conventional_commits | bool | true | Enforce Conventional Commits specification. |
| commit | message_pattern | str | "" (no custom pattern) | Custom regex pattern for commit message validation. When set, this pattern replaces the auto-generated Conventional Commits regex entirely, making it possible to enforce custom formats such as JIRA smart commits (e.g., "^PROJ-\\d+: .+"). When message_pattern is set (non-empty) it takes precedence over conventional_commits. |
| commit | subject_capitalized | bool | false | Subject must start with a capital letter. |
| commit | subject_imperative | bool | false | Subject must be in imperative mood. Forms of verbs can be found at imperatives.py |
| commit | subject_max_length | int | 80 | Maximum length of the subject line. |
| commit | subject_min_length | int | 5 | Minimum length of the subject line. |
| commit | allow_commit_types | list[str] | ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "build", "ci"] | Allowed commit types when conventional_commits is true. |
| commit | allow_merge_commits | bool | true | Allow merge commits. |
| commit | allow_revert_commits | bool | true | Allow revert commits. |
| commit | allow_empty_commits | bool | true | Allow empty commits. |
| commit | allow_fixup_commits | bool | true | Allow fixup commits (e.g., "fixup! |
| commit | allow_wip_commits | bool | true | Allow work-in-progress commits (e.g., "WIP: |
| commit | require_body | bool | false | Require a body in the commit message. |
| commit | ignore_authors | list[str] | [] (none ignored) | List of commit authors or co-authors (Co-authored-by: lines) to bypass all commit checks. Useful for bots (e.g., "dependabot[bot]", "coderabbitai[bot]"). |
| commit | author_email_pattern | str | ^.+@.+$ |
Custom regex for the author email check. When empty, the built-in default pattern is used. This option only takes effect when the author_email check is enabled (-e / --author-email). |
| commit | author_name_pattern | str | "" (built-in default) | Custom regex for the author name check. When empty, the built-in default pattern is used (it is not disabled). This option only takes effect when the author_name check is enabled (-n / --author-name). |
| commit | require_signed_off_by | bool | false | Require "Signed-off-by" line in the commit message footer. |
| commit | ai_attribution | str | "ignore" | AI attribution policy. "forbid" rejects any commit containing known AI tool signatures (Claude Code, Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, and generic AI model patterns). "ignore" disables the check. This feature is a response to the industry-wide discussion on AI disclosure in open source (Linux kernel Assisted-by: trailer, CPython, VS Code, Apache, Fedora policies). |
| branch | conventional_branch | bool | true | Enforce Conventional Branch specification. |
| branch | allow_branch_types | list[str] | ["feature", "bugfix", "hotfix", "release", "chore", "feat", "fix", "build", "ci", "docs", "perf", "refactor", "style", "test", "ai", "claude", "codex", "copilot", "cursor", "dependabot", "renovate"] | Allowed branch types when conventional_branch is true. The default is a superset of the Conventional Branch spec: the spec types (feature, bugfix, hotfix, release, chore) plus the Conventional Commit types (build, ci, docs, perf, refactor, style, test), AI agent prefixes (ai, claude, codex, copilot, cursor) and bot prefixes (dependabot, renovate). For strict spec-only validation, set this option explicitly (e.g. ["feature", "bugfix", "hotfix", "release", "chore"]). |
| branch | allow_branch_names | list[str] | [] (empty list) | Additional standalone branch names allowed when conventional_branch is true (e.g., ["develop", "staging"]). By default, master, main, HEAD, and PR-* are always allowed. |
| branch | require_rebase_target | str | "" (no requirement) | Target branch for rebase requirement. If not set, no rebase validation is performed. |
| push | allow_force_push | bool | true | Allow force pushes. Set to false to block force pushes when used as a pre-push hook or with --no-force-push. |
| branch | ignore_authors | list[str] | [] (none ignored) | List of authors to ignore (i.e., always allow). |