Found while building an enforcement page for conventionalbranch.org, where commit-check is the tool I wanted to recommend for the CI and local layers. Running it against the specification's conformance fixtures turned up 6 of 34 disagreements, plus a separate bypass that is a bug regardless of the spec.
Version tested: commit-check 2.15.0, default configuration.
Root cause
All of it comes from one line in rule_builder.py:
def _build_conventional_branch_regex(self, allowed_types, allowed_names) -> str:
types_pattern = "|".join(allowed_types)
base_names = ["master", "main", "HEAD", "PR-.+"]
all_names = base_names + allowed_names
names_pattern = ")|(".join(all_names)
return rf"^({types_pattern})\/.+|({names_pattern})"
which produces:
^(feature|bugfix|...)\/.+|(master)|(main)|(HEAD)|(PR-.+)
1. No end anchor, and ^ binds to the first alternative only
| has the lowest precedence, so the ^ applies to ^(types)\/.+ alone. (master), (main), (HEAD) and (PR-.+) are unanchored on both sides, and nothing in the pattern has a $. Since the matcher is re.match, the effect is that any branch name beginning with main, master or HEAD skips validation entirely:
| Branch |
commit-check |
| mainline |
accepted |
| masterkey |
accepted |
| main-foo |
accepted |
| HEADless |
accepted |
These are ordinary names someone could plausibly pick, and they are silently exempted from the check the user enabled. This one is a bug on its own terms — it has nothing to do with which specification you follow.
2. The description is .+, so none of the naming rules are enforced
Everything after the slash is accepted, which means the lowercase/hyphen rules are not checked at all:
| Branch |
Why the spec rejects it |
commit-check |
| feature/new--login |
consecutive separators |
accepted |
| feature/-new-login |
leading separator |
accepted |
| feature/new-login- |
trailing separator |
accepted |
| fix/header_bug |
underscore not allowed |
accepted |
| release/v1.-2.0 |
dot followed by separator |
accepted |
3. develop is rejected
develop is one of the three trunk branches in the specification, alongside main and master, but only the latter two are in base_names. main and master pass, develop fails, and the user has to discover allow_branch_names to fix it — which is how #335 was resolved. Since conventional_branch: true is meant to mean "follow the specification", the three trunk branches arguably belong in the default.
Reproduction
#!/bin/sh
set -e
pip install --quiet commit-check
tmp=$(mktemp -d); cd "$tmp"
git init -q -b main . && git commit -q --allow-empty -m "feat: init"
check() {
git checkout -q -B "$1" 2>/dev/null
if commit-check --branch >/dev/null 2>&1; then got=accepted; else got=rejected; fi
[ "$got" = "$2" ] && mark="ok " || mark="FAIL"
printf '%s %-24s spec=%-8s commit-check=%s\n' "$mark" "$1" "$2" "$got"
}
check mainline rejected
check masterkey rejected
check main-foo rejected
check HEADless rejected
check feature/new--login rejected
check feature/-new-login rejected
check feature/new-login- rejected
check fix/header_bug rejected
check release/v1.-2.0 rejected
check develop accepted
All ten report FAIL on 2.15.0.
Not reporting: the extra branch types
docs/, ci/, refactor/, test/ and friends being accepted is deliberate — that was #494, aligning branch types with commit types. That is a reasonable product decision and this issue does not ask to revert it. It does mean the default config is intentionally broader than the specification, which is worth a sentence in the docs so nobody assumes "default" and "spec-conformant" are the same thing.
Suggested fix
The narrow fix is to group and anchor the whole thing, and to validate the description:
return rf"^(?:(?:{types_pattern})/{description_pattern}|(?:{names_pattern}))$"
The broader one, and the reason I am filing this rather than sending a one-line patch: the specification publishes fixtures.json precisely so implementations can prove they conform. Wiring it into commit-check's CI would have caught all three of these, and would keep catching them:
# 34 cases, language-agnostic, one URL, no dependency on the spec repo
spec = json.load(urlopen("https://conventionalbranch.org/v1.1.0/spec.json"))
cases = json.load(urlopen("https://conventionalbranch.org/...fixtures..."))
That would also make commit-check the reference implementation in a way that is verifiable rather than asserted, which is worth more than any wording on either website. Happy to send the PR for either or both if you would like.
Disclosure: I maintain both projects, so treat this as a note-to-self filed in public rather than an outside bug report.
Found while building an enforcement page for conventionalbranch.org, where commit-check is the tool I wanted to recommend for the CI and local layers. Running it against the specification's conformance fixtures turned up 6 of 34 disagreements, plus a separate bypass that is a bug regardless of the spec.
Version tested: commit-check 2.15.0, default configuration.
Root cause
All of it comes from one line in rule_builder.py:
which produces:
1. No end anchor, and ^ binds to the first alternative only
| has the lowest precedence, so the ^ applies to ^(types)\/.+ alone. (master), (main), (HEAD) and (PR-.+) are unanchored on both sides, and nothing in the pattern has a $. Since the matcher is re.match, the effect is that any branch name beginning with main, master or HEAD skips validation entirely:
These are ordinary names someone could plausibly pick, and they are silently exempted from the check the user enabled. This one is a bug on its own terms — it has nothing to do with which specification you follow.
2. The description is .+, so none of the naming rules are enforced
Everything after the slash is accepted, which means the lowercase/hyphen rules are not checked at all:
3. develop is rejected
develop is one of the three trunk branches in the specification, alongside main and master, but only the latter two are in base_names. main and master pass, develop fails, and the user has to discover allow_branch_names to fix it — which is how #335 was resolved. Since conventional_branch: true is meant to mean "follow the specification", the three trunk branches arguably belong in the default.
Reproduction
All ten report FAIL on 2.15.0.
Not reporting: the extra branch types
docs/, ci/, refactor/, test/ and friends being accepted is deliberate — that was #494, aligning branch types with commit types. That is a reasonable product decision and this issue does not ask to revert it. It does mean the default config is intentionally broader than the specification, which is worth a sentence in the docs so nobody assumes "default" and "spec-conformant" are the same thing.
Suggested fix
The narrow fix is to group and anchor the whole thing, and to validate the description:
The broader one, and the reason I am filing this rather than sending a one-line patch: the specification publishes fixtures.json precisely so implementations can prove they conform. Wiring it into commit-check's CI would have caught all three of these, and would keep catching them:
That would also make commit-check the reference implementation in a way that is verifiable rather than asserted, which is worth more than any wording on either website. Happy to send the PR for either or both if you would like.
Disclosure: I maintain both projects, so treat this as a note-to-self filed in public rather than an outside bug report.