Validate commit messages locally, in CI, or from stdin using Conventional Commits rules.
ccval helps you:
- catch invalid commit messages before merge or release
- enforce team rules for types, scopes, headers, and footers
- validate one commit, a branch range, or a message from stdin or a file
Once ccval is installed, validate the last commit in the current repository:
ccvalValidate all commits on your branch:
ccval -- origin/main..HEADUse the built-in strict preset:
ccval -p strictValidate a commit message from stdin:
printf 'feat: add validation\n' | ccval --stdinChoose the option that fits your workflow.
cargo install ccvalbrew install andrey-fomin/tap/ccvalDownload prebuilt binaries for Linux, macOS, and Windows from GitHub Releases.
Run ccval without installing it locally:
printf 'feat: add validation\n' | docker run --rm -i andreyfomin/ccval:distroless --stdinAvailable image tags (moving convenience tags):
-
andreyfomin/ccval:latest- Alpine-based, includes Git support, about 11 MB -
andreyfomin/ccval:distroless- minimal image, no Git support, about 1 MB -
Use
:distrolesswhen you only need stdin or file validation -
Use
:latestwhen you need to validate commits from a Git repository
The release workflow also publishes versioned tags, including :1, :1.2, :1.2.3, and matching -distroless variants such as :1.2.3-distroless.
For Git-based validation in a mounted repository, use --trust-repo only when you control the repository and Git fails with a detected dubious ownership warning:
docker run --rm -v "$(pwd)":/repo -w /repo andreyfomin/ccval --trust-repoIf macOS blocks a downloaded binary, remove the quarantine attribute:
xattr -d com.apple.quarantine /path/to/ccvalYou can also right-click the binary, choose Open, and confirm the prompt.
ccvalccval -- origin/main..HEADccval -r /path/to/repo -- HEAD~10..HEAD--repository changes where Git reads commits from. Config auto-discovery still happens in the current working directory, so use --config too when the target repository has its own config file:
ccval -r /path/to/repo -c /path/to/repo/conventional-commits.yaml -- HEAD~10..HEADccval --file .git/COMMIT_EDITMSGccval -TOr with an explicit repository path:
ccval -r /repo -T -- HEAD~5..HEADUse a commit-msg hook to validate each commit message before Git creates the commit.
Create .git/hooks/commit-msg with this content:
#!/bin/sh
set -eu
exec ccval --file "$1"Then make it executable:
chmod +x .git/hooks/commit-msgThis hook expects ccval to be installed and available on your PATH.
By default, if no config file is found and no preset is specified, ccval only checks whether the commit message is parseable as a Conventional Commit.
When a config file is present or a preset is provided, ccval also applies the validation rules defined there so you can enforce team-specific rules.
To enforce team-specific rules, add a config file such as conventional-commits.yaml.
Minimal example:
preset: strict
type:
values:
- feat
- fix
- docs
scope:
required: trueThe same idea in TOML:
preset = "strict"
[type]
values = ["feat", "fix", "docs"]
[scope]
required = trueWith this config:
strictenables useful default formatting rules- only
feat,fix, anddocsare allowed - every commit must include a scope such as
feat(api): add endpoint
When --config is not provided, ccval looks for these files in order:
conventional-commits.yamlconventional-commits.ymlconventional-commits.tomlconventional-commits.json
Use a custom config path when needed:
ccval -c .github/conventional-commits.yaml -- origin/main..HEADccval includes two built-in presets:
default- formatting rules for description spacing and newline handling in body and footer valuesstrict-defaultplus header length limits and common type and scope restrictions
When a config file and preset are both used:
-p/--presettakes precedence overpreset:in the config file- rules omitted in your config inherit from the preset
regexes: []clears preset regex rules for that field
Use a preset from the command line without changing your config file:
ccval -p strictUse ccval in GitHub Actions to validate pull request commit ranges or pushed commits.
on: pull_request
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: andrey-fomin/ccval@v0Common options:
Use a custom config:
- uses: andrey-fomin/ccval@v0
with:
config: .github/conventional-commits.yamlUse a built-in preset:
- uses: andrey-fomin/ccval@v0
with:
preset: strictOverride git arguments:
- uses: andrey-fomin/ccval@v0
with:
git-args: origin/main..HEAD --no-mergesNote: git-args is parsed as a whitespace-separated list of arguments by the action; shell-style quoting/escaping is not supported, and arguments that contain spaces cannot be passed as a single argument. Auto-detected behavior such as adding --no-merges applies only when git-args is not set.
Limit checked commits:
- uses: andrey-fomin/ccval@v0
with:
max-commits: "250"The action supports push and pull_request events, discovers conventional-commits.yaml or .github/conventional-commits.yaml, skips merge commits in its auto-detected ranges, skips deleted-ref and zero-commit pushes, and limits validation to 100 commits by default.
For push events, it prefers the exact pushed range from local history. If the pushed before commit is not available locally, it falls back to the default-branch merge-base when possible and otherwise fails with a clear error.
If either the push event itself or the selected commit range exceeds max-commits, the action skips validation, emits a warning, and exits successfully.
Make sure your workflow fetches enough history, for example with fetch-depth: 0, so the required commit range is available.
Use @v0 to track the latest compatible v0.x.y release, or pin a specific release tag such as @v0.3.1. For a fully immutable reference, pin a commit SHA.
ccval checks commit messages in two steps:
- Parse the message structure
- Apply validation rules from your config
A message can be parseable and still fail validation.
For example, feat: add api may parse successfully but fail stricter formatting rules.
Read more:
PARSING.mdfor commit message structure and parse errorsVALIDATION.mdfor available fields, rules, and configuration examples
Usage: ccval [-c <path>] [-p <preset>] [-r <path>] [-T] [-- <git-log-args>...]
ccval [-c <path>] [-p <preset>] --stdin
ccval [-c <path>] [-p <preset>] -f <path>
ccval -h
Validates commit messages from stdin, a file, or Git.
Modes:
(default) Validate commit(s) from git log
Use -- <git-log-args>... to pass arguments to git log
Default: -1 (last commit)
--stdin Read commit message from stdin
-f, --file <path> Read commit message from a file
-h, --help Show this help message
Options:
-c, --config <path> Use a custom config file path
-p, --preset <name> Use a built-in preset (default or strict)
-r, --repository <path>
Path to Git repository working tree
Cannot be used with --stdin or --file
-T, --trust-repo Trust the repository despite ownership mismatch
Useful when running in containers or accessing
repositories owned by other users
Requires git mode (cannot use with --stdin or --file)
Examples:
ccval # validate last commit
ccval -- origin/main..HEAD # validate commits on branch
ccval -p strict # validate last commit with strict preset
ccval -r /path/to/repo # validate last commit in specific repo
ccval -T # validate last commit, trusting repo
ccval -r /repo -T # validate in container
printf 'feat: msg\n' | ccval --stdin
ccval --file .git/COMMIT_EDITMSG
ccval -c config.yaml --stdin
0success64usage error65content invalid66input unavailable70internal error74I/O error77permission error78config error
PARSING.mdexplains the supported commit message structureVALIDATION.mdlists fields, rule types, presets, and examplesCHANGELOG.mdtracks release historyRELEASING.mddocuments the release process