Skip to content

Commit 529417d

Browse files
claudepiotrlaczkowski
authored andcommitted
fix(ci): repair the PR-title check, which could never pass
The `regexp` input in PRECOMMITS.yml was written with doubled backslashes inside a single-quoted YAML scalar, which has no backslash escapes. Every `\\` therefore reached JavaScript as a literal backslash, so the class `[a-z,A-Z,0-9,\\-,\\_,\\/,:]` contained a `\` (0x5C) to `,` (0x2C) range and `new RegExp` threw before reading any title: SyntaxError: Invalid regular expression: Range out of order in character class The job died in five seconds whatever the pull request was called, and the `pre-commit run` step after it never executed - so the workflow verified nothing at all. Replace it with the pattern the repository already uses for commit messages (the `validate-commit-msg` hook), so one convention governs both and a title accepted locally is accepted in CI. Scope stays optional and the type list matches the hook, which is what the history on `main` actually uses. tests/test_ci_workflow_config.py compiles the pattern, asserts it stays identical to the hook entry, and checks it against real titles from `main` plus malformed ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1CErYvpggWAogJ9MDVqj4
1 parent 05eeaee commit 529417d

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

.github/workflows/PRECOMMITS.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,14 @@ jobs:
2929
- name: Check Pull Request Title
3030
uses: Slashgear/action-check-pr-title@main
3131
with:
32-
regexp: '(break|build|ci|docs|feat|fix|perf|refactor|style|test|ops|hotfix)\\([a-z,A-Z,0-9,\\-,\\_,\\/,:]+\\)(:)\\s{1}([\\w\\s]+)' # Regex the title should match.
32+
# Kept identical to the `validate-commit-msg` hook in
33+
# .pre-commit-config.yaml so a title that passes locally passes here.
34+
# The previous pattern was double-escaped for a YAML single-quoted
35+
# scalar (which has no backslash escapes), so `[a-z,A-Z,0-9,\-,...]`
36+
# reached JavaScript containing a `\`-to-`,` range and RegExp threw
37+
# "Range out of order in character class" before reading any title -
38+
# the check could never pass, whatever the PR was called.
39+
regexp: '^(break|build|ci|docs|feat|fix|perf|refactor|style|test|ops|hotfix|release|maint|init|enh|revert|reformat)(\([\w.,\-()/]+\))?(!)?:\s.+$'
3340
- name: Getting changed files list
3441
id: files
3542
uses: jitterbit/get-changed-files@v1

tests/test_ci_workflow_config.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
r"""The PR-title check in CI must use a pattern that actually compiles.
2+
3+
``.github/workflows/PRECOMMITS.yml`` passed its ``regexp`` through a
4+
single-quoted YAML scalar, which has no backslash escapes, so every ``\\`` in
5+
the pattern reached JavaScript as a literal backslash. The character class
6+
``[a-z,A-Z,0-9,\\-,\\_,\\/,:]`` therefore contained a range from ``\`` (0x5C)
7+
to ``,`` (0x2C) and ``new RegExp`` threw::
8+
9+
SyntaxError: Invalid regular expression: Range out of order in character class
10+
11+
The action crashed before reading any title, so the job failed in five seconds
12+
whatever the pull request was called - and the ``pre-commit run`` step after it
13+
never executed. Nothing in CI could pass this workflow.
14+
"""
15+
16+
from __future__ import annotations
17+
18+
import re
19+
from pathlib import Path
20+
21+
import pytest
22+
import yaml
23+
24+
REPO_ROOT = Path(__file__).resolve().parent.parent
25+
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "PRECOMMITS.yml"
26+
PRE_COMMIT_CONFIG = REPO_ROOT / ".pre-commit-config.yaml"
27+
28+
pytestmark = pytest.mark.skipif(not WORKFLOW.exists(), reason="workflow not present")
29+
30+
31+
def _pr_title_pattern() -> str:
32+
workflow = yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
33+
steps = workflow["jobs"]["pre-commit-checks"]["steps"]
34+
step = next(s for s in steps if s.get("name") == "Check Pull Request Title")
35+
return step["with"]["regexp"]
36+
37+
38+
def _commit_msg_pattern() -> str:
39+
config = yaml.safe_load(PRE_COMMIT_CONFIG.read_text(encoding="utf-8"))
40+
hook = next(h for repo in config["repos"] for h in repo["hooks"] if h["id"] == "validate-commit-msg")
41+
return hook["entry"]
42+
43+
44+
def test_the_pr_title_pattern_compiles():
45+
"""A pattern that does not compile fails every pull request."""
46+
re.compile(_pr_title_pattern())
47+
48+
49+
def test_the_pr_title_pattern_matches_the_commit_message_hook():
50+
"""One convention, one pattern.
51+
52+
A title that the local ``commit-msg`` hook accepts must not be rejected by
53+
CI, and vice versa; the two drift apart the moment they are written twice.
54+
"""
55+
assert _pr_title_pattern() == _commit_msg_pattern()
56+
57+
58+
@pytest.mark.parametrize(
59+
"title",
60+
[
61+
"refactor: production readiness audit",
62+
"fix(core): restore integrations re-exports",
63+
"feat(ui/backend): add the security module",
64+
"docs: document model serving",
65+
"maint: sync version to 2.2.0 across all sources [skip ci]",
66+
"fix!: drop the deprecated endpoint",
67+
],
68+
)
69+
def test_titles_the_project_already_uses_are_accepted(title):
70+
"""Every one of these appears in the history of ``main``."""
71+
assert re.match(_pr_title_pattern(), title), f"{title!r} is rejected by the PR-title check"
72+
73+
74+
@pytest.mark.parametrize(
75+
"title",
76+
[
77+
"chore: not one of the allowed types",
78+
"a title with no type at all",
79+
"feat:no space after the colon",
80+
"feat: ",
81+
],
82+
)
83+
def test_malformed_titles_are_still_rejected(title):
84+
"""The check has to keep checking something."""
85+
assert not re.match(_pr_title_pattern(), title), f"{title!r} should not pass the PR-title check"

0 commit comments

Comments
 (0)