# conventional-pre-commit blocks commit messages that break the pattern

> conventional-pre-commit is a pre-commit hook and a standalone command that check a Git commit message against Conventional Commits formatting, with options to allow a chosen set of types, restrict scopes, force a scope, or apply strict mode that also rejects fixup and merge commits. It suits a team that wants the convention enforced rather than documented, and the hook has no runtime dependencies.

**compilerla/conventional-pre-commit** — A pre-commit hook that checks commit messages for Conventional Commits formatting

- Repository: https://github.com/compilerla/conventional-pre-commit
- Stars: 559 · Forks: 74
- Language: Python
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/compilerla-conventional-pre-commit

## A hook, and a command you can also run by hand

conventional-pre-commit is a pre-commit hook that checks commit messages for Conventional Commits formatting, and it works with Python 3.8 or newer. The package has no runtime dependencies at all, which is unusual for a tool in this space and means the hook environment installs instantly. It can be used two ways. As a hook, the input is supplied automatically, since the hook reads the current commit message, and as a command line script the message file is an argument. The standalone form is:

```shell
pip install conventional-pre-commit
```

and then the script itself takes an optional list of types to allow and the file holding the message:

```shell
conventional-pre-commit [types] input
```

So a project that permits only feature, fix, chore, ci and test commits would run it as conventional-pre-commit feat fix chore ci test .git/COMMIT_MSG. The command line form matters for CI, where you can validate a range of commit messages without going through a developer's local hooks, and for scripts that check pull request titles.

## The error message tells you what to fix

The failure output is worth reading because it is what a contributor sees. A rejected commit shows the message flagged, the line stating that the commit message does not follow Conventional Commits formatting, and a link to the specification. With the verbose flag the output expands to show the expected pattern, a type(scope): subject line with an extended body beneath it, and a list of the errors found. In the example the error is that the expected type value is missing from a fixed list: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test. The output then tells the user the command to run, git commit --edit --file=.git/COMMIT_EDITMSG, which reopens the message in the editor with the offending text still in place. The fixed list of types is the part to be aware of, because a project that uses a type outside it, such as a chore scope or a house-specific prefix, has to pass that type explicitly in the types argument.

## Four flags decide how strict the check is

The command line help lists the options and they map to the decisions a team actually has to make. The positional types argument is the optional list of Conventional Commit types to support, for example feat fix chore, and it comes first because that is what most projects change. --scopes takes a comma-separated list of scopes to support with no spaces, for example api,client, and it restricts which scope names are accepted rather than which types are. --force-scope requires the commit to have a scope defined, which is how you catch a bare feat: with no parenthesis. --strict forces the commit to follow the formatting strictly and disallows fixup! and merge commits, which matters because both are messages the tool would otherwise accept. --verbose prints more verbose error output, and --no-color disables colour in the output, which is what you want in a CI log.

## Wiring it into pre-commit, and the default_install_hook_types detail

The pre-commit configuration is short but has one line that is easy to miss. You set default_install_hook_types to include pre-commit and commit-msg, and then add the repo entry with a rev pinned to a git sha or tag, the hook id conventional-pre-commit, the stage set to commit-msg, and an args list. The default_install_hook_types entry matters because pre-commit does not install the commit-msg hook type by default, so without it the hook is configured but never runs. The entry looks like this:

```yaml
repos:
  - repo: https://github.com/compilerla/conventional-pre-commit
    rev: <git sha or tag>
    hooks:
      - id: conventional-pre-commit
        stages: [commit-msg]
        args: []
```

and arguments are supplied either on the command line or through the hooks args property, with the args list in the configuration carrying the flags and the allowed types, for example args: [--strict, --force-scope, feat, fix, chore, test, custom]. The README notes that when the tool runs as a pre-commit hook the input is supplied automatically with the current commit message, so the same command works in both modes without a different argument list.

## Using the checker as a library

The formatter module exposes a function you can call from your own Python code, which is the part that turns this from a hook into a component. The import is from conventional_pre_commit.format import is_conventional, and the function takes a message and an optional list of types. The documented examples show the three cases that matter. A conventional message returns True, a non-conventional one returns False, and a message with a custom type returns True when custom is passed in the types list. That last case is the extension point, because it means your own tooling can enforce the same rule with the same type list your hook uses, rather than reimplementing the pattern match. The package manifest confirms the shape, with a single console script entry point pointing at conventional_pre_commit.hook:main, and a single package directory in the setuptools configuration.

## Packaging, releases and the upstream it grew from

The project builds with setuptools and setuptools-scm, and the version is dynamic, taken from the Git tag rather than written into the source, which is the usual reason to use that backend. The dev extras are black, build, coverage, flake8, pre-commit, pytest and setuptools_scm, and the tool configuration is thorough: black at a line length of 127 targeting Python 3.11, coverage with branch measurement and relative file paths, pyright over the package and the tests, and pytest with the test paths pointed at the tests directory and a list of directories to skip. Releases to PyPI and GitHub are triggered by pushing a tag, with a regular release tagged as vX.Y.Z and a pre-release as vX.Y.Z-preN, and the version generally follows Semantic Versioning. The repository also ships a devcontainer configuration, a compose file that builds a development image and mounts the source, and a note that the project is inspired by matthorgan's pre-commit-conventional-commits. The release history shows v4.4.0 in February 2026 after a v4.4.0-pre1, with v4.3.0 in October 2025, and the last push was on 2026-10-12.

## Conclusion

Adopt conventional-pre-commit when your team already follows Conventional Commits and wants a machine to enforce it on every commit, and you want to be able to narrow the allowed types to what your project actually uses. Do not adopt it if your team has no convention yet, since a hook that rejects commits creates friction without a standard to enforce. Verify first that your pre-commit configuration installs the commit-msg hook type, that the allowed type list matches the prefixes you use, and that strict mode is off if your workflow relies on fixup or merge commits.

## FAQ

### How do I add conventional-pre-commit to a project?

Create a blank .pre-commit-config.yaml if needed, set default_install_hook_types to include pre-commit and commit-msg, add the repo entry for compilerla/conventional-pre-commit with a pinned rev and the hook id conventional-pre-commit at stage commit-msg, then run pre-commit install --install-hooks. The default_install_hook_types entry matters because pre-commit does not install the commit-msg hook type by default.

### What commit message types does conventional-pre-commit allow by default?

The verbose error output lists the expected types as build, chore, ci, docs, feat, fix, perf, refactor, revert, style and test. You can narrow that by passing your own list as the positional types argument or in the hook args, for example feat fix chore test custom, and a custom type is then accepted.

### What do the conventional-pre-commit options do?

The types positional argument lists the Conventional Commit types to support, --scopes restricts which scope names are accepted as a comma separated list, --force-scope requires a scope to be defined, --strict forces strict formatting and disallows fixup! and merge commits, --verbose prints more verbose error output and --no-color disables colour in the output.

### Can I use conventional-pre-commit outside a git hook?

Yes. Install it with pip install conventional-pre-commit and run conventional-pre-commit with an optional types list and a file containing the message, for example conventional-pre-commit feat fix chore ci test .git/COMMIT_MSG. There is also a Python API, the is_conventional function in conventional_pre_commit.format, which takes a message and an optional types list and returns a boolean.

## Sources

- [compilerla/conventional-pre-commit on GitHub](https://github.com/compilerla/conventional-pre-commit)
- [Issues](https://github.com/compilerla/conventional-pre-commit/issues)
- [License: Apache-2.0](https://github.com/compilerla/conventional-pre-commit/blob/main/LICENSE)
- [README](https://github.com/compilerla/conventional-pre-commit/blob/main/README.md)
- [Releases](https://github.com/compilerla/conventional-pre-commit/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/compilerla-conventional-pre-commit
