GitHub Action wrapping DivergentCodes/commitlint:
Conventional Commits linting for PR titles and commit messages with no
third-party actions — the only nested uses: is GitHub's own
actions/setup-go, and the linter is a zero-dependency Go binary installed
from source.
Pin to a full commit SHA (with a version comment) — the strongest supply-chain posture, since a moved or compromised tag can't silently swap the action:
name: pr-lint
on:
pull_request:
types: [opened, edited, synchronize, reopened]
permissions:
contents: read
jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<full-sha> # required for commits-mode
- uses: DivergentCodes/commitlint-action@<full-sha> # v1.0.1
with:
pr-title-mode: block # squash-merge title is load-bearing
commits-mode: warn # advisory; intermediate commits vanish at squashactions/checkout is required whenever commits-mode is not off, because
the action reads the PR's commits from git. With commits-mode: off the title
comes from the event payload and no checkout is needed.
Tag pinning (@v1) also works and is fine for internal repos, but SHA
pinning is recommended for anything security-sensitive.
The edited trigger matters: it re-runs the check when a PR title is fixed,
so a failed title clears without pushing a commit.
When PRs are squash-merged, the PR title becomes the commit message on
main — so the title is load-bearing (it drives semantic-release versioning)
and is linted in block mode. The individual commits inside the PR are
discarded at squash, so they're linted in warn mode: contributors get
feedback without being blocked on work-in-progress commit messages. If you
merge with rebase or merge commits instead, set commits-mode: block.
| Input | Default | Meaning |
|---|---|---|
version |
latest |
commitlint version to go install; pin a tag (e.g. v1.1.2) in production |
pr-title-mode |
block |
block fails the check, warn reports only, off skips |
commits-mode |
warn |
same modes, applied to each PR commit |
types |
conventional set | comma-separated allowed types |
scopes |
any | comma-separated allowed scopes |
require-scope |
false |
require a (scope) |
max-subject-length |
72 |
subject length limit |
github-token |
github.token |
fetches the commitlint module while its repo is private; unused once public |
permissions: contents: read is sufficient — enough for actions/checkout
to fetch the commits. The action makes no API calls and never writes anything.
With commits-mode: off it reads only the event payload.
The linter is installed with a plain go install, which resolves through the
Go module proxy with checksum-database verification. No token is involved.
If that fetch fails — which it will if the linter is being pulled from a
private fork or mirror — the action retries with github-token, rewriting only
github.com/DivergentCodes/ URLs so the token is never offered to another host
or org. That retry sets GOPRIVATE, which bypasses the proxy and checksum
database; it is scoped to the fallback precisely so the ordinary path keeps its
verification.
Both this action and DivergentCodes/commitlint are public, so no token or
extra configuration is needed — paste the workflow from Usage and it
works.
The github-token input remains for the case where the linter is fetched from
a private fork or mirror; it is unused otherwise.
Runs on ubuntu-latest (and other GitHub-hosted runners) out of the box: it
uses actions/setup-go to install the linter and reads commits with git. On
self-hosted runners, ensure Go and git are available.
- "subject must be
type(scope)?: description" on a PR title → edit the title to a conventional form (e.g.feat: ...,fix(api): ...); theeditedtrigger re-runs the check automatically. - PR commits failing unexpectedly → they're linted in
warnmode by default (non-blocking). If they block,commits-modeis set toblock. - Type rejected → it's not in the allowed set; pass
types:to extend it.
Releases are automatic on merge to main, with autogenerated changelogs in
the release notes (no committed changelog file). The version comes from the
merged commit's conventional type:
| Merged commit | Bump | Example |
|---|---|---|
feat!: / BREAKING CHANGE |
major | v1.2.3 → v2.0.0 |
feat: |
minor | v1.2.3 → v1.3.0 |
fix: / perf: |
patch | v1.2.3 → v1.2.4 |
docs: / ci: / chore: … |
none | no release |
The major-version alias (v1) moves to each new release automatically, so
@v1 always resolves to the newest compatible version. Pushing a v* tag by
hand still works for re-cuts.
Reference a release by SHA (preferred) or tag.