Git Commit Message Linter
Validate commit messages against the Conventional Commits specification. Checks type, scope, subject length, body format and breaking change markers.
Linter, Pre-commit Hook, or CI Gate?
Conventional Commits compliance can be enforced at three different points, and each catches problems at a different cost. This browser-based linter checks a message before it's ever committed โ the cheapest possible point to fix a mistake, since nothing has to be rewritten or force-pushed. A local pre-commit hook running commitlint via Husky or lefthook catches the same issues automatically on every git commit, without anyone needing to remember to paste the message somewhere first, but it requires every contributor to have the hook installed locally, and hooks are trivially bypassed with --no-verify. A CI-level check running commitlint --from origin/main against every commit on a pull request is the strictest and hardest-to-bypass option, since it runs on the server regardless of local setup โ but by the time it fails, the offending commit already exists in the branch history and needs an interactive rebase to fix, which is a meaningfully more disruptive fix than catching it before the commit was made. Most teams that take Conventional Commits seriously run more than one of these, using the browser tool or local hook as the fast feedback loop and CI as the backstop that nothing slips past.
Parsing a Commit Header
The linter runs the message header through a regular expression that decomposes it into a type, an optional scope in parentheses, an optional breaking-change marker (!), and the subject text following the colon. Each extracted component is then checked independently against the Conventional Commits v1.0 specification: the type must be one of the eleven recognized values (feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert), the subject must be non-empty and is flagged if it exceeds roughly 72 characters, the first letter of the subject should be lowercase, and a trailing period on the subject is flagged. A breaking change is detected two ways โ either the ! character immediately following the type or scope, or a BREAKING CHANGE: token appearing as a footer in the commit body โ and either signal alone is sufficient to mark the commit as breaking.
Why Commit Messages Are a Security Surface Too
Commit messages are plaintext, permanently retained in git history, and often mirrored to external systems โ pushed to a public GitHub mirror, synced into a project management tool via a webhook integration, or surfaced in a changelog generated straight from commit text. That combination makes them an easy place to accidentally leak information that shouldn't be public: an internal ticket description pasted verbatim into a commit body, a customer name mentioned while explaining a bug fix, or worse, a credential or internal hostname typed into the message while debugging and never meant to be committed at all. None of that is caught by format validation โ a perfectly Conventional-Commits-compliant message can still leak sensitive detail in its subject or body โ so teams that care about this run a secret-pattern scan (similar to what a .env validator does for config files) across commit messages in CI, separately from format linting, rather than assuming one check covers both concerns.
Grading a Real Commit Message
Consider the message Fixed the login bug. typed as a first attempt. Run through the linter, it fails on four counts: there's no recognized type prefix at all (the whole string is being read as a bare subject), the first letter is capitalized, the subject ends with a period, and โ most consequentially for automation โ because there's no fix: type, a tool like semantic-release has no signal to trigger a version bump at all. Rewritten as fix(auth): correct session token expiry check, the same intent now parses cleanly: type fix, scope auth, a lowercase subject with no trailing period. Semantic-release now sees a valid fix commit and knows to cut a PATCH release, and a changelog generator groups it correctly under "Bug Fixes" instead of leaving it out entirely because it didn't match the expected grammar.
Frequently Asked Questions
What is the Conventional Commits format?
Conventional Commits is a specification for writing structured git commit messages in the format type(scope): subject. The type declares what kind of change the commit makes: feat for a new feature, fix for a bug fix, docs for documentation changes, style for formatting-only changes, refactor for code restructuring without behavioral change, perf for performance improvements, test for test additions or fixes, build for build system changes, ci for CI/CD pipeline changes, chore for maintenance tasks, and revert for reverting a previous commit. The scope is an optional noun in parentheses identifying the section of the codebase affected, and the subject is a short imperative description of the change.
Why should I use Conventional Commits?
Conventional Commits enables a wide range of automation that is impossible with free-form commit messages. Tools like semantic-release and standard-version parse your commit history to automatically determine the next semantic version number โ a feat commit triggers a MINOR bump, a fix triggers a PATCH bump, and any breaking change triggers a MAJOR bump. The same parse generates a structured CHANGELOG.md grouped by change type. Teams using Conventional Commits spend significantly less time writing release notes, reviewing what changed between versions, or deciding how to bump version numbers. Pre-commit hooks with commitlint enforce the standard locally so non-conformant messages never reach the remote repository.
How do I enforce Conventional Commits in a CI pipeline?
The most common approach is to install commitlint (npm install --save-dev @commitlint/cli @commitlint/config-conventional) and add a Husky pre-commit hook that runs the linter before every local commit. In CI, you can add a pipeline step that runs commitlint --from origin/main to check every commit on the feature branch against the conventional commits ruleset, blocking the merge if any commit fails. GitHub Actions supports this via the wagoid/commitlint-action marketplace action, and GitLab CI can run the same npm command as a pipeline job. For teams that want to enforce conventions without adding Node.js dependencies, some git hosting platforms offer built-in commit message pattern matching as a branch protection rule.