Skip to content

git_commit_message

Validate commit-message shape via regex, max-subject-length, or required-body. At least one of the three must be set; combine all three for full Conventional-Commits-style enforcement. Subject length counts characters, not bytes (a 50-char emoji subject is 50, not 200).

Two modes, selected by the optional since: field:

  • HEAD-only (default, since: omitted): validate the tip commit. Right shape for push-trigger CI and post-commit hooks.
  • Range (since: set): validate every commit reachable from HEAD but not from since. Right shape for pull_request-trigger CI, where actions/checkout checks out a synthetic merge commit whose subject the rule would always flag.

since: accepts anything git rev-parse resolves: a 40-char or abbreviated SHA, a branch (origin/main), a tag (v1.2.3), or a relative ref (HEAD~5). The rule walks <since>..HEAD oldest-first, validates each commit, and emits one violation per failing commit with the short SHA + a subject snippet so you know which to amend.

Use alint's standard template syntax for environment values:

  • {{env.VAR}} requires VAR to be set and non-empty.
  • {{env.VAR | default('value')}} supplies a fallback, such as since: "{{env.ALINT_BASE_SHA | default('origin/main')}}" for local runs.

GitHub Actions expressions are evaluated only in workflow files, not inside .alint.yml; export the expression to an environment variable in the workflow and read it through {{env.VAR}} in the alint config. The older POSIX-style ${VAR} / ${VAR:-default} syntax still works for this field but is deprecated and will be removed in v1.0.

In range mode, merge commits are skipped by default (include_merges: false). Merge subjects in PR contexts are typically actions/checkout-generated or maintainer-resolved and uninteresting. Set include_merges: true to lint them too. Has no effect when since: is unset; combining include_merges: true with no since: is a load-time error.

  • No Git, git not on PATH, or the lint root is not a repository: a resource_read analysis failure.
  • since: ref doesn't resolve: hard error with a shallow-clone hint. The common cause is actions/checkout@v4 with its default fetch-depth: 1, which doesn't fetch the base ref's commits. Use fetch-depth: 0 to fetch full history.
  • Range is empty or HEAD is unborn: normal pass. No commits, no policy input.

GitHub Actions PR-validation recipe

Section titled "GitHub Actions PR-validation recipe"
.github/workflows/lint.yml
name: lint
on:
pull_request:
branches: [main]
jobs:
alint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Range mode walks <base>..HEAD. fetch-depth: 0 makes
# both refs reachable; the default depth of 1 leaves
# the base ref out of local objects and the rule errors.
fetch-depth: 0
- name: alint check
env:
ALINT_BASE_SHA: ${{ github.event.pull_request.base.sha }}
uses: asamarts/alint@8d7a60f6642cb1b59537202ae81df7d951a078ad # v0.22.0
OptionTypeRequiredDefaultDescription
include_mergesbooleanfalseWhen validating a range (since: set), include merge commits. Defaults to false because merge commits in PR contexts are typically the synthetic merge actions/checkout produces (with an auto-generated subject the rule would always flag) or maintainer-resolved merges from the base branch. Has no effect when since: is unset; combining include_merges: true with no since: is a load-time error.
patternstringnullRust-regex pattern the full message (subject + body, joined with newlines) must match. Use (?s) to make . match newlines.
requires_bodybooleanfalseWhen true, the message must have a non-empty body, that is, at least one line of content after the subject's blank-line separator.
sincestringnullGit ref to use as the base of the commit range. When set, validates every commit in <since>..HEAD instead of just HEAD. Accepts anything git rev-parse does: SHA (full or abbreviated), branch (origin/main), tag (v1.2.3), or relative ref (HEAD~5). Supports alint's template syntax, so CI can pass a SHA through an environment variable (for example, since: "{{env.ALINT_BASE_SHA | default('origin/main')}}"). The older POSIX ${VAR} / ${VAR:-default} form is deprecated and will be removed in v1.0.
subject_max_lengthinteger (>= 1)nullMaximum number of characters allowed in the subject line. Common values: 50 (Tim Pope's recommendation), 72 (GitHub PR-title cutoff).

Plus the common level, id, and when fields. This rule analyses the whole repository, so it takes no paths or expect_matches. This table is generated from the JSON Schema; option types and defaults are authoritative.

A commit with a non-conventional subject

Section titled "A commit with a non-conventional subject"

The rule fires on this repository:

  • the repository

    ✗ error conventional-commits

    commit HEAD: commit message does not match pattern `^(feat|fix|chore): ` (subject: "wip nonsense")

  • README.md✓
README.md
# demo

With this .alint.yml:

version: 1
rules:
- id: conventional-commits
kind: git_commit_message
pattern: '^(feat|fix|chore): '
level: error

committed with this history (oldest first):

wip nonsense (adds README.md)

alint check reports:

Terminal window
--- Repository-level -----------------------------------------------------------
x error conventional-commits
commit HEAD: commit message does not match pattern
`^(feat|fix|chore): ` (subject: "wip nonsense")
Summary (1 violation):
x 1 error
0 passing * 1 failing

A commit that follows the convention

Section titled "A commit that follows the convention"

This repository is compliant:

  • README.md✓
README.md
# demo

With this .alint.yml:

version: 1
rules:
- id: conventional-commits
kind: git_commit_message
pattern: '^(feat|fix|chore): '
level: error

committed with this history (oldest first):

feat: add the readme (adds README.md)

alint check reports:

Terminal window
v All 1 rule(s) passed.