# GitHub App

The hosted [Commit Check App](https://github.com/apps/commit-check)
runs the same rule engine as the CLI, against the same config file, with no
workflow file and no CI minutes. Install it once, push, and every commit gets
a **Commit Check** result that says what failed, the value that failed, and
how to fix it. Not every rule applies where the App runs; the
[list](#which-rules) is below.

[Install the App](https://github.com/apps/commit-check){ .md-button .md-button--primary }

Choosing between the App and the [GitHub Action](github-actions.md): see
[Where to run it](integrations.md#app-or-action). Both read the same config and
report the same rule IDs, so running both is fine.

## Install

On the [App's page](https://github.com/apps/commit-check), click **Install** and
pick the account to install on. For an organization, choose **All
repositories** and every repository, including ones created later, is covered;
with **Only select repositories**, only those are checked and new ones have to
be added by hand. A personal account works the same way.

There is nothing to add to a repository. A repository that already has a
`cchk.toml` or `commit-check.toml` is checked against it from the next push. One
without a config file takes the account's shared one, if the organization or
user keeps a `cchk.toml` in its `.github` repository (see
[Across an organization](#across-an-organization)), and otherwise is checked
with the defaults, advisory only (see [Without a config file](#without-a-config-file)).

## What you get

On every push to a branch and on every pull request, each commit gets a check
run named **Commit Check** (a push of more than 50 commits has its newest 50
checked, and the older ones get one neutral "not checked" result) on the Checks tab, in the commit's status and in the
merge box. (Tag pushes and branch deletions carry nothing to check; commits by
bots are skipped, except for AI coding agents that commit as an app, and squash
mode checks a pull request as one message — both
[below](#bots-re-runs-and-commits-checked-twice).) The result reads the same
way everywhere:

- a one-line verdict — `All 4 checks passed`, or `Commit message and branch failed (2 of 4 checks)`;
- for a failure, a table of what failed, the value that failed and the rule
  ID, linked to the [rule reference](../rules.md);
- **How to fix**: the rule's own explanation and suggestion;
- the full listing of everything that was checked, folded away;
- the version of the rule engine that produced it.

**Details** on the check opens that report; nothing lives on any other site.

### Which rules

The App runs the commit message, author and branch rules — [CC001–CC016](../rules.md#commit-message-rules),
[CC101–CC102](../rules.md#author-rules) and [CC201–CC202](../rules.md#branch-rules) —
with the commit's own branch as the branch under test. One thing it does not
do: the push, file and tag rules ([CC301–CC304](../rules.md#push-rules),
[CC401](../rules.md#tag-rules)) run where a push or a tag happens, in the
[pre-commit hooks](pre-commit.md) and the CLI.

[CC202](../rules.md#cc202) (`require_rebase_target`) is decided from GitHub's
own comparison of the head with the target branch rather than from the clone,
which holds only the commits under test. A failure says how many commits
behind the target the branch is.

### Pull requests

Every commit of a pull request is checked on its own, fork pull requests
included, and pushing more commits checks the new ones. A team that
squash-merges keeps none of those commits, so it can check the result instead:

```toml title="cchk.toml"
[pull_request]
check = "squash"
```

The pull request then gets one result on its head commit, against the message
a squash merge would land — built the way GitHub builds it from the
repository's merge settings, pull request number included. Editing the title or
description re-runs it. The [configuration page](../configuration.md#pull-requests-every-commit-or-the-squash-message)
has the table of what is checked under each merge setting.

### Bots, re-runs, and commits checked twice

- Commits by bots (any author whose name ends in `[bot]`: dependabot,
  renovate, and the like) are skipped. AI coding agents that commit as an app
  are the exception: commits by Devin, Jules, Copilot's coding agent, Claude,
  Codex, Amazon Q, Cursor and Factory are checked like anyone's. For finer
  control use [`ignore_authors`](../configuration.md#every-option), which the
  rule engine applies itself.
- **Re-run** on the Checks tab checks a commit again — after a config change,
  say. In squash mode it re-checks the pull request.
- A commit pushed to a branch that has an open pull request raises both a push
  and a pull request event; the App checks it once.

## Configuration

The App reads the same file the CLI reads, as it stands at the head of the
push or pull request, in the same four places: `cchk.toml`, `commit-check.toml`, `.github/cchk.toml`,
`.github/commit-check.toml`. Every option on the
[configuration page](../configuration.md) that belongs to a rule the App runs
applies, precedence included; the `[push]`, `[files]` and `[tag]` sections are
read but have nothing to act on here. Nothing is App-specific to set apart
from `[pull_request]` (above) and `[jira]` (below); the CLI and the Action ignore
them.

[`inherit_from`](organization.md) works, and a `github:` target is fetched with
the App's own credentials, so a shared config in a private `.github` repository
is reachable from the App even though the CLI, which fetches anonymously,
cannot read it. A target the App cannot fetch either is left to the CLI, where
inheritance [fails open](organization.md#other-sources).

### Without a config file

A repository with no config file first looks to its account: a `cchk.toml` in
the owner's `.github` repository — organizations and personal accounts both
have one — is applied as if it were the repository's own, and the report says
so in its last lines. Its failures are enforced, because the owner chose those
rules.

A repository with no config file **and** no shared one has not chosen its
rules, so its result is **advisory**: failures are reported in full, but the check is neutral — titled,
say, `Commit message and branch would fail (2 of 4 checks, not enforced)` — and never blocks a merge. Below the
fixes the report says why, and gives the smallest file that turns enforcement
on:

```toml title="cchk.toml"
[commit]
conventional_commits = true

[branch]
conventional_branch = true  # false if branch names are free-form
```

Once the file is pushed, a failure turns the check red instead of neutral. A
passing result is the same with or without it.

!!! warning "Required means nothing until there is a config"

    GitHub treats a neutral check as passing. Making **Commit Check** a required
    check on a repository with no config file blocks nothing. Add the file
    first.

### Jira ticket references

The App can additionally require a ticket reference in every message. This
check is App-side, so the CLI and the Action ignore the section:

```toml title="cchk.toml"
[jira]
required = true
pattern = "PROJ-\\d+"        # default: any KEY-123
suggestion = "Add the ticket, e.g. PROJ-123"
```

It reports as `JIRA jira-ticket` in the same table as the other rules. It
checks the reference's shape, not whether the ticket exists.

## Enforcing it

The App marks a commit; it cannot refuse a push. What keeps a violating change
off the protected branch is a **required status check** named `Commit Check`
in the branch's ruleset or branch protection, where the repository's plan
allows one. With that in place a failing result blocks the merge button and a
passing one clears it.

Add the config file first and the required check second, for the reason in
the box above.

## Across an organization

Installing on the organization with **All repositories** covers every
repository. Keeping their rules in step takes one file: a `cchk.toml` in the
organization's `.github` repository (root or `.github/`, either name). Every
repository without a config file of its own is checked against it, new
repositories included, with nothing added to any of them. A personal account's
`.github` repository works the same way for that account's repositories. A repository that
needs something different commits its own file, which takes the
organization's place, or starts from it with one
[`inherit_from`](organization.md) line and overrides what it must.

The App reads the shared file with its own credentials, so a private
`.github` repository works. The [Across an organization](organization.md)
guide walks through the file, inheritance, and how to roll a policy out
without a wall of red.

## Plans

Public repositories are free on any account, and everything on a personal
account is free, and both stay free. Only an organization's private
repositories will be paid, on a Team plan that is not on sale yet:

| Plan | Price | Covers |
|---|---|---|
| Open Source | Free | Public repositories, on any account |
| Personal | Free | Private repositories on a personal account |
| Team | Coming soon | Private repositories in an organization |

The Team plan is not tiered by seat count. Twenty of you and twenty thousand of
you pay the same, so growing the team never changes the bill.

Today none of this is enforced: every repository is checked at no charge while
the App is new. When the plan goes live, a private organization repository
without it gets a neutral check saying so — nothing is blocked, nothing is
rejected — and this page will say so before it happens.

The [CLI](../getting-started.md), the [pre-commit hook](pre-commit.md),
the [GitHub Action](github-actions.md) and the [MCP server](mcp.md) are MIT
licensed and always free. If you would rather not pay for the App on an
organization's private repositories, the Action reads the same config and
reports the same rule IDs — it just costs you CI minutes instead.

## Privacy

The App fetches the commits under test, the configuration file (the
repository's own or its account's shared one) and the repository metadata the
checks need, such as the squash-merge settings and the comparison with a rebase
target, and stores nothing: no database, no copy of any message, author or
config. The [privacy page](../privacy.md) lists the permissions it asks for and
what each one is used for.

## When something is off

**No check appears.** The App is not installed on that repository (check the
installation's repository list), the push was a tag or a branch deletion, or
every commit was by a bot other than an AI coding agent.

**A rule you never turned on is failing.** The repository has no config file
and the defaults apply — which is why the result is advisory. The
[Troubleshooting](../troubleshoot.md) page covers the defaults; the starter
config above turns them into a policy.

**The check says "Commit Check could not run".** That is an error, not a
verdict: the App hit a problem checking the commit and says so rather than
staying silent. **Re-run** it; if it keeps failing,
[open an issue](https://github.com/commit-check/commit-check/issues) with the
text of the report.

**The shared config is not being applied.** The App has to be installed on
the `.github` repository too (it is, with **All repositories**),
the file has to be at one of the four paths, and the repository must have no
config file of its own — one that does uses its own. A `github:` `inherit_from`
the App cannot fetch falls back to the CLI, which fails open; check the
[organization guide](organization.md#other-sources) for the sources it accepts.
