Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ below and to the page that documents the feature properly.

| Version | What changed | Documented in |
|---|---|---|
| [2.20.0](#v2200) | A commit authored by an AI tool rejected (CC017); Continue, Mistral Vibe and Qwen Code recognised; subject length `0` turns the rule off; Python 3.10 dropped | [CC017](rules.md#cc017) · [CC013](rules.md#cc013) · [CC004](rules.md#cc004) |
| [2.19.5](#v2195) | GitHub merge queue branches accepted by default; aider's default co-author trailer recognised; exit 2 when git cannot read the repository | [CC201](rules.md#cc201) · [CC013](rules.md#cc013) · [Exit codes](troubleshoot.md#exit-codes) |
| [2.19.4](#v2194) | `agents/` branches accepted by default; Cursor's `Made-with: Cursor` trailer recognised | [CC201](rules.md#cc201) · [CC013](rules.md#cc013) |
| [2.19.3](#v2193) | A quoted `"false"` for a true/false setting refused instead of read as true | [Every option](configuration.md#every-option) |
Expand All @@ -36,6 +37,48 @@ below and to the page that documents the feature properly.
| [2.5.0](#v250) | Organization-wide config with `inherit_from` | [Across an organization](guides/organization.md) |
| [2.0.0](#v200) | Configuration moved from YAML to TOML — breaking | [Migrating from v1](migration.md) |

## v2.20.0 (2026-11-08) { #v2200 }

### Added

* **[CC017](rules.md#cc017) rejects a commit whose author is an AI tool** —
the message rules read only the message, so an agent that commits under
its own identity (`claude[bot]`, `google-labs-jules[bot]`,
`devin-ai-integration[bot]`, OpenHands, an `(aider)` name) passed with a
clean message, even one that credits a person as co-author. Under
`ai_attribution = "forbid"` or `"disclose"` such a commit now fails.
`ignore_authors` skips it and `warn` can report it without failing.
See PR [#661](https://github.com/commit-check/commit-check/pull/661).

* **[CC013](rules.md#cc013)–[CC016](rules.md#cc016) recognise Continue,
Mistral Vibe and Qwen Code** — their default commit signatures
(`Generated with [Continue](https://continue.dev)`,
`Generated by Mistral Vibe.` with `vibe@mistral.ai`, and
`Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>`) passed every
policy.
See PRs [#657](https://github.com/commit-check/commit-check/pull/657) and
[#659](https://github.com/commit-check/commit-check/pull/659).

### Changed

* **`subject_max_length = 0` and `subject_min_length = 0` turn the rule
off** — `0` used to mean a limit of zero characters, so a project that
wanted only the AI check had to pick a large number instead. See
[Checking AI attribution and nothing else](guides/policies.md#checking-ai-attribution-and-nothing-else).
See PR [#660](https://github.com/commit-check/commit-check/pull/660).

* **Python 3.11 or newer is required** — Python 3.10 reached end of life, and
the `tomli` dependency is gone with it. The GitHub Action needs it too from
v2.19.0: on `ubuntu-22.04`, add `actions/setup-python` first (see
[GitHub Action](guides/github-actions.md)).
See PR [#662](https://github.com/commit-check/commit-check/pull/662).

### Fixed

* **`[commit] require_verified` no longer warns as an unknown setting** — the
GitHub App reads it, and the CLI now leaves it alone.
See PR [#656](https://github.com/commit-check/commit-check/pull/656).

## v2.19.5 (2026-11-04) { #v2195 }

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion docs/compare/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Replace the hook:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-message
```
Expand Down
6 changes: 3 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ Used from a hook definition, with no config file anywhere in the repository:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-message
args:
Expand Down Expand Up @@ -387,8 +387,8 @@ the same thing twice, so read the description rather than the cell:
| 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. Judged on the first word's form, so a verb no list contains is still accepted — see [CC003](rules.md#cc003). |
| 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 | subject_max_length | int | 80 | Maximum length of the subject line. `0` turns the check off. |
| commit | subject_min_length | int | 5 | Minimum length of the subject line. `0` turns the check off. |
| 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. |
Expand Down
2 changes: 1 addition & 1 deletion docs/example.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ pushed:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-no-force-push
stages: [pre-push]
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ name.

!!! tip "Supported Python versions"

Commit Check supports Python 3.10 through 3.15, on Linux, macOS and Windows.
Commit Check supports Python 3.11 through 3.15, on Linux, macOS and Windows.

Running it as a [pre-commit hook](guides/pre-commit.md)
or a [GitHub Action](guides/github-actions.md) needs no
Expand Down
12 changes: 12 additions & 0 deletions docs/guides/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ jobs:
The Action reads the same `cchk.toml` as the CLI, so a repository that already
has one needs no Action-specific configuration.

!!! note "Python 3.11 or newer"

The Action runs on the runner's own Python, and since v2.19.0 it needs
3.11 or newer. The `*-latest` GitHub-hosted runners have it.
`ubuntu-22.04` ships 3.10, so add a setup step before the Action there:

```yaml
- uses: actions/setup-python@v7
with:
python-version: "3.11"
```

## Inputs

| Input | Default | What it does |
Expand Down
78 changes: 77 additions & 1 deletion docs/guides/policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ Docs: https://commit-check.com/rules/#cc013

Recognised signatures are co-author and sign-off lines naming Claude Code,
GitHub Copilot, Codex, Gemini, Cursor, Devin, Aider, Windsurf, Tabby, Amp,
OpenHands, Factory Droid and Jules, the disclosure trailers `Assisted-by:` and
OpenHands, Factory Droid, Jules, Continue, Mistral Vibe and Qwen Code, the
disclosure trailers `Assisted-by:` and
`Generated-by:`, vendor marks such as `🤖 Generated with Claude Code`, Amp's
`Amp-Thread-ID:`, the Copilot coding agent's `Agent-Logs-Url:` and Cursor's
`Made-with: Cursor`, and generic AI model names. This is the
Expand All @@ -166,6 +167,40 @@ also commits as its own account, but every commit it makes carries an
mechanism against those who aren't. Be clear with yourself about which of
those you are buying.

### Checking AI attribution and nothing else

`ai_attribution` sits alongside the message rules that are on by default, so
`--message` still applies Conventional Commits and the subject length limits
(80 characters at most, 5 at least). A project that has its own commit style
and only wants the AI check turns those off:

```toml title="cchk.toml"
[commit]
ai_attribution = "forbid"
conventional_commits = false
subject_max_length = 0 # 0 turns the length rule off
subject_min_length = 0
```

Or, in the GitHub Action, without a config file:

```yaml
- uses: commit-check/commit-check-action@v2
env:
CCHK_AI_ATTRIBUTION: forbid
CCHK_CONVENTIONAL_COMMITS: false
CCHK_SUBJECT_MAX_LENGTH: 0
CCHK_SUBJECT_MIN_LENGTH: 0
with:
message: true
branch: false
```

Leave the length limits on and a long subject fails the job too. Dependency
bots write long ones: Renovate's `Update dependency
com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer to
v20261103` is 95 characters.

### Asking for disclosure instead

Most projects that allow AI assistance ask to be told about it.
Expand Down Expand Up @@ -288,6 +323,47 @@ triagers judge each contribution by whether its author can explain it in their
own words. A policy like that leaves nothing for commit-check to enforce, so
keep `ai_attribution` at its default, `"ignore"`.

### Recipes from real projects

Many projects enforce their AI policy with a hand-written grep step in CI.
Each policy below fits in a few lines of `cchk.toml`. Add
`conventional_commits = false` if your project does not use Conventional
Commits (see [Checking AI attribution and nothing else](#checking-ai-attribution-and-nothing-else)).

| Project | Its rule for commits | `cchk.toml` |
|---|---|---|
| [Kubernetes](https://www.kubernetes.dev/docs/guide/pull-requests/) | No AI co-author, no AI sign-off, no `Assisted-by` | `ai_attribution = "forbid"` |
| [Linux kernel](https://docs.kernel.org/process/coding-assistants.html) | Every commit signed off by a person; an AI never signs off | `ai_attribution = "disclose"`, `require_signed_off_by = true`, top-level `warn = ["ai_disclosure"]` |
| Nextcloud | `Assisted-by: AGENT_NAME:MODEL_VERSION` on AI-assisted commits; an AI never signs off | `ai_attribution = "disclose"`, `ai_disclosure_pattern = '^[^:\s]+:\S+'` |
| [pip](https://github.com/pypa/pip/blob/main/AI_POLICY.md), [attrs](https://github.com/python-attrs/attrs/blob/main/.github/AI_POLICY.md) | No AI co-author; disclosure not required | `ai_attribution = "disclose"`, top-level `warn = ["ai_disclosure"]` |
| Fedora, FluxCD | Disclose with `Assisted-by:` | `ai_attribution = "disclose"`, `ai_disclosure_trailers = ["Assisted-by"]` |

The Nextcloud recipe, in full:

```toml title="cchk.toml"
[commit]
ai_attribution = "disclose"
ai_disclosure_pattern = '^[^:\s]+:\S+' # AGENT_NAME:MODEL_VERSION
```

`Assisted-by: ClaudeCode:claude-sonnet-4-6` passes. `Assisted-by: Claude Code`
fails CC014 because it names no model, and `Signed-off-by: Claude
<noreply@anthropic.com>` fails CC016.

Some projects ask every commit to say whether AI was used, with
`Assisted-by: none` when it was not. `disclose` only asks when a commit shows
signs of AI, so pair it with a `message_pattern` that requires the trailer:

```toml title="cchk.toml"
[commit]
ai_attribution = "disclose"
message_pattern = '(?ms).*^Assisted-by:[ \t]*\S'
```

`message_pattern` replaces the Conventional Commits check, so this recipe
cannot be combined with `conventional_commits = true`. A commit with no
`Assisted-by:` line fails CC001; `Assisted-by: none` passes.

### Exempting automation

Bots that legitimately carry AI trailers can be excluded:
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/pre-commit.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Add Commit Check to `.pre-commit-config.yaml`:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-message
- id: check-branch
Expand Down Expand Up @@ -68,7 +68,7 @@ Options can be passed as hook arguments, which keeps everything in one file:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-message
args:
Expand All @@ -90,7 +90,7 @@ colon, and lets the commit through:
```yaml title=".pre-commit-config.yaml"
repos:
- repo: https://github.com/commit-check/commit-check
rev: v2.19.5
rev: v2.20.0
hooks:
- id: check-message
args: [--fix]
Expand Down
5 changes: 2 additions & 3 deletions docs/guides/python-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pass 1
To apply a repository's own policy, load its file and pass it along:

```python
import tomllib # on Python 3.10: import tomli as tomllib
import tomllib

from commit_check.api import validate_message

Expand All @@ -137,8 +137,7 @@ with open("cchk.toml", "rb") as f:
result = validate_message("feat: add streaming support", config=config)
```

On Python 3.10, `tomli` is already installed: commit-check depends on it
there. `inherit_from` is not followed this way; only the file's own keys
`inherit_from` is not followed this way; only the file's own keys
apply. Every key is in the [configuration reference](../configuration.md).

## Fixing until it passes
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ description: Commit policy as code for teams and AI agents. Messages, branches,
</div>
<ul class="cc-wrap cc-facts">
<li>MIT licensed, no account needed</li>
<li>Python 3.10 – 3.15 on Linux, macOS and Windows</li>
<li>Python 3.11 – 3.15 on Linux, macOS and Windows</li>
<li>Signed build provenance on every release</li>
<li>CLI and hook work with any Git host</li>
</ul>
Expand Down
49 changes: 47 additions & 2 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ same root cause.
**Options**

* `commit.subject_max_length` — the limit, in characters. Defaults to `80`.
`50` and `72` are the other conventional choices.
`50` and `72` are the other conventional choices. `0` turns the rule off.

### subject-min-length (CC005) { #cc005 }

Expand Down Expand Up @@ -341,6 +341,7 @@ fix: reject config files with a null inherit_from
**Options**

* `commit.subject_min_length` — the minimum, in characters. Defaults to `5`.
`0` turns the rule off.

### allow-merge-commits (CC006) { #cc006 }

Expand Down Expand Up @@ -563,7 +564,8 @@ git commit --amend --signoff

Rejects a commit whose message carries any AI attribution: a co-author or
sign-off line naming Claude Code, Copilot, Codex, Gemini, Cursor, Devin,
Aider, Windsurf, Tabby, Amp, OpenHands, Factory Droid or Jules, a disclosure
Aider, Windsurf, Tabby, Amp, OpenHands, Factory Droid, Jules, Continue,
Mistral Vibe or Qwen Code, a disclosure
trailer such as `Assisted-by:` or `Generated-by:`, a vendor's own mark such
as `🤖 Generated with Claude Code`, Amp's `Amp-Thread-ID:` trailer, the
`Agent-Logs-Url:` trailer the Copilot coding agent adds or Cursor's
Expand Down Expand Up @@ -697,6 +699,49 @@ Suggest: Use "Assisted-by: Claude" in place of the AI sign-off, then sign off yo

* `commit.ai_attribution` — set to `"disclose"` to enable this rule.

### ai-author (CC017) { #cc017 }

**What it does**

Under `ai_attribution = "forbid"` or `"disclose"`, rejects a commit whose
author is an AI tool: `claude[bot]`, `copilot-swe-agent[bot]`,
`google-labs-jules[bot]`, `devin-ai-integration[bot]`, OpenHands, a name
ending in `(aider)`, or any name at a tool's own address such as
`noreply@anthropic.com`. The author is read the same way as a
`Co-authored-by:` line, so the two never disagree about who is a tool.

An existing commit is judged by the author it records. A message piped in,
or one the `commit-msg` hook checks before the commit exists, is judged by
the author git would record: `GIT_AUTHOR_NAME` and `GIT_AUTHOR_EMAIL` when
set, otherwise `user.name` and `user.email`. The GitHub Action passes each
pull request commit's own author this way.

**Why is this bad?**

The message rules read only the message. Several agents commit under their
own identity and add no trailer at all, or credit the person as the
co-author, so a commit made entirely by a tool can carry a clean message.
Putting the tool in the author's place is the strongest attribution there
is, and it is what GitHub shows as the contributor.

**Example**

```text
CC017 ai-author check failed ==> google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
An AI tool is the commit author: Jules
Suggest: Author the commit yourself: git commit --amend --reset-author
```

The commit fails even with a human `Co-authored-by:` line.

**Options**

* `commit.ai_attribution` — set to `"forbid"` or `"disclose"` to enable this
rule.
* `commit.ignore_authors` — authors listed here are skipped, for a bot you
trust to commit on its own.
* Top-level `warn = ["ai_author"]` reports the rule without failing the run.

## Author rules

### author-name (CC101) { #cc101 }
Expand Down