Catch bad commits before they merge — on every pull request.
Docs · Rules · CLI · GitHub App · MCP server
The GitHub Action for Commit Check.
It checks every commit of a pull request — message, branch, author, and
optionally the PR title — against your cchk.toml, and reports the result in
the job summary, as annotations on the diff and, if you want, as a PR comment.
Add a workflow, e.g. .github/workflows/commit-check.yml:
name: Commit Check
on:
pull_request:
branches: 'main'
jobs:
commit-check:
runs-on: ubuntu-latest
permissions: # use permissions because use of pr-comments
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
# Required. With the default fetch-depth: 1 the clone holds only GitHub's
# merge commit: the PR's own commits cannot be listed, author checks are
# skipped, and the action warns and falls back to checking HEAD alone.
fetch-depth: 0
- uses: commit-check/commit-check-action@v2
with:
message: true
branch: true
author-name: false
author-email: false
job-summary: true
pr-comments: trueImportant
Keep fetch-depth: 0. A shallow clone holds only GitHub's merge commit,
whose Merge <sha> into <sha> subject passes the default rules, so every
pull request would look green; the action warns when it happens. On
pull_request_target, also check out refs/pull/<number>/merge.
Runs on ubuntu-latest, macos-latest and windows-latest. Self-hosted
runners need a few tools — see Good to know.
| Input | Default | What it does |
|---|---|---|
message |
true |
Check every commit message against Conventional Commits |
branch |
true |
Check the branch name against Conventional Branch |
author-name |
false |
Check each commit's author name |
author-email |
false |
Check each commit's author email |
pr-title |
false |
Check the pull request title against Conventional Commits — the one that matters for squash merges. Pull request events only |
pr-comments |
false |
Post the report as a pull request comment, edited in place on later runs. Needs pull-requests: write; skipped on fork pull requests |
job-summary |
true |
Write the report to the job summary |
dry-run |
false |
Report failures as warnings and always exit 0 |
Tip
pull_request does not fire when a title is edited. With pr-title: true,
add types: [opened, synchronize, reopened, edited] to re-check a renamed
pull request.
The action reads the repository's cchk.toml or commit-check.toml — the same
file the CLI and the pre-commit hook use. Any setting can also come from a
CCHK_* environment variable, no config file needed:
- uses: commit-check/commit-check-action@v2
env:
CCHK_SUBJECT_CAPITALIZED: "true"
CCHK_REQUIRE_SIGNED_OFF_BY: "true"
CCHK_AI_ATTRIBUTION: "forbid"
CCHK_ALLOW_COMMIT_TYPES: "feat,fix,docs,chore"Priority: inputs > environment variables > config file > defaults. A rule
listed under the config's top-level warn is reported in full but never fails
the run. Every key: configuration reference.
A failing run opens with a count and a table of only what failed — each rule ID links to its documentation, each commit to itself — with the full tree one click away:
Commit Check
❌ 2 of 4 checks failed
Scope Checked value Failed checks Commit 2/2 (5584f46) bad msgCC001 message Branch Feature/Add-LoginCC201 branch Show all 4 checks
Commit message ✔ PR title (feat: add login page) ✔ Commit 1/2 (d87faca) (feat: add login page) ✖ Commit 2/2 (5584f46) (1 failure) CC001 message value: bad msg The commit message should follow Conventional Commits. Suggest: Use <type>(<scope>): <description> Branch ✖ Branch (1 failure) CC201 branch value: Feature/Add-Login The branch should follow Conventional Branch. Suggest: Rename the branch to "feature/Add-Login" (git branch -m feature/Add-Login) Fix: feature/Add-Logincommit-check <version> · Rules reference
The rest follow the same layout:
- Passed: one line,
✅ All 3 checks passed, with the tree folded away. - Warned: a rule under
warngets its own ⚠ row and counts as passed. - Skipped: a run where nothing was validated — typically a bot listed in
ignore_authors— reads⊘, never✔.
The step log prints the same tree, plus one annotation per finding on the
Files changed tab. With pr-comments: true the same report is posted to the
pull request, and later runs edit that one comment instead of adding more.
Structured check results as JSON, available to downstream steps via
fromJSON:
- uses: commit-check/commit-check-action@v2
id: commit-check
with:
dry-run: true # (1)
- name: Inspect results
run: |
echo "Status: ${{ fromJSON(steps.commit-check.outputs.result).status }}"
echo "Scopes: ${{ toJSON(fromJSON(steps.commit-check.outputs.result).scopes) }}"- Without
dry-run, a failing check ends the job before any later step runs. Usedry-run(orcontinue-on-error) when a downstream step is meant to read the result and decide for itself.
The top-level status is one of:
status |
Meaning | Exit code |
|---|---|---|
pass |
every check passed | 0 |
warn |
nothing failed, but a rule listed under the config's warn found something |
0 |
skip |
every check declined to run (for example the author is in ignore_authors) |
0 |
fail |
at least one check failed | 1 (0 with dry-run) |
Only fail is ever non-zero; warn exists so a downstream step can react to a
bent-but-not-broken policy without the run turning red:
- if: fromJSON(steps.commit-check.outputs.result).status == 'warn'
run: echo "passed with warnings"Each entry in scopes has a label (PR title, Commit 2/3, Branch, ...),
a status like the ones above, a sha (the full hash of the commit a
Commit N/M or Commit message scope checked; empty for the others) and the
check outcomes (rule_id, check, status, value, error, suggest,
fix, docs_url) exactly as produced by commit-check --format json, so
downstream jobs can build their own reports or gate on individual rules.
All three run the same commit-check engine against the same
commit-check.toml / cchk.toml; they differ in where they run and what they
can see.
| GitHub Action (this repo) | pre-commit hook | Commit Check GitHub App | |
|---|---|---|---|
| Where it runs | In your workflow, on the runner, after the push | On the contributor's machine, at git commit / git push |
Hosted by commit-check; installed on the repository, no workflow file |
| What it checks | Every PR commit's message, plus the PR title, branch and author checks you enable; renders a job summary, annotations, a PR comment and the result output |
Message (commit-msg stage), branch, author; tag, force-push and files (pre-push) — one commit at a time, before it exists |
Every commit of a push or pull request: message, branch, author (the PR title only in squash mode); reported as one Commit Check check run per commit |
| When to pick it | You want enforcement in CI that a contributor cannot skip, per-rule outputs for later steps, or you run on GitHub Enterprise Server / need CCHK_* overrides |
You want the fastest feedback and to stop bad commits before they are pushed; pair it with the Action, since hooks are opt-in | You want zero YAML and no Actions minutes, or feedback on fork pull requests without the Action's read-only-token limits |
Most teams pair the pre-commit hook (fast, local) with the Action (enforced): the hook catches a bad message before it is pushed, and the Action is why CI fails when a contributor did not install the hook.
Apache
discovery-unicamp
Texas Instruments
OpenCADC
Extrawest
Chainlift
Mila
RLinf
Collective
cpp-linter
and many more.
Runner requirements
The action is a composite step and uses what the runner already has:
- Python 3.10 or newer on
PATH(python3, orpythonon Windows). Nosetup-pythonstep is needed on GitHub-hosted runners. Everything the action installs goes under$RUNNER_TEMP, never into your checkout. ghCLI — used to verify the build-provenance attestation of thecommit-checkwheel before installing it. Present on GitHub-hosted images; install it on self-hosted runners or the attestation step fails. Only thecommit-checkwheel is attested; PyGithub and the transitive dependencies are pinned byrequirements.txtbut not verified.- Network access to PyPI and
api.github.com— the pinned wheels are downloaded once per run and the attestation is fetched from GitHub. gitonPATH, and a checkout withfetch-depth: 0(see above).
There is currently no input to skip attestation verification.
Fork and Dependabot pull requests
A pull request from a fork gets a read-only GITHUB_TOKEN, so pr-comments
cannot post there. The action skips the comment with a ::warning:: and
notes it in the job summary; the check, the annotations and the summary still
work. For feedback on the pull request itself, use the
Commit Check GitHub App (free on public
repositories) or run the action on pull_request_target — both are covered in
Fork pull requests.
Dependabot pull requests are not forks, but their pull_request runs also get
a read-only token by default. The pull-requests: write grant in the
usage example is honoured for them; without it the action logs a
::warning:: on the 403 and leaves the report in the job summary. Adding
dependabot[bot] to ignore_authors skips those pull requests altogether.
[](https://commit-check.com)
@v2 follows the latest v2 release; pin a full version or a commit SHA if you
prefer. Releases follow Semantic Versioning. Upgrading
from v1? See the v2.0.0 release notes.
Questions and ideas go to Discussions, bugs and feature requests to Issues.