Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/scripts/check_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
no-op that passes on rc-* so hotfix/regression PRs are
not forced to carry the full template).

Release-promotion PRs — a 'chore(release):' title from an 'rc-*' head branch,
the promote step of the release train — are exempt from both checks (V2-1092):
they are mechanical version-bump PRs with no issue or template of their own.

PR fields are read from the environment (set by the workflow from the event
payload): PR_TITLE, PR_BODY, PR_BRANCH, PR_BASE.

Expand Down Expand Up @@ -38,6 +42,14 @@
r"linear\.app/[^/\s]+/issue/[A-Za-z][A-Za-z0-9]*-[0-9]+", re.IGNORECASE
)

# Release-promotion PRs (the promote step of the release train) are mechanical
# version-bump PRs opened from an rc-* branch. Both checks self-pass on them —
# they must pass rather than be skipped because linear-link/pr-template are
# required status checks. Matched on BOTH the conventional-commit title prefix
# and the rc-* head branch so an ordinary PR cannot opt out via its title alone.
RELEASE_TITLE = re.compile(r"^chore\(release\):")
RC_BRANCH = re.compile(r"^rc-")

# Canonical section headings, exactly as they appear in the template, keyed by
# their lower-cased form. Used so failure messages name the real heading.
CANONICAL_HEADINGS = {
Expand Down Expand Up @@ -72,6 +84,12 @@ def strip_comments(text):
return re.sub(r"<!--.*?-->", "", text, flags=re.DOTALL)


def release_promotion():
return bool(
RELEASE_TITLE.match(env("PR_TITLE")) and RC_BRANCH.match(env("PR_BRANCH"))
)


def linear_ref(*parts):
"""Return the first Linear URL/key found in the given (comment-free) parts."""
haystack = "\n".join(parts)
Expand All @@ -96,6 +114,12 @@ def sections(body):


def check_linear():
if release_promotion():
ok(
f"✅ linear-link not enforced on release-promotion PRs "
f"(chore(release) from '{env('PR_BRANCH')}')."
)

# Strip comments from the body so the template's own example does not count.
ref = linear_ref(env("PR_TITLE"), strip_comments(env("PR_BODY")), env("PR_BRANCH"))
if ref:
Expand All @@ -110,6 +134,12 @@ def check_linear():


def check_template():
if release_promotion():
ok(
f"✅ pr-template not enforced on release-promotion PRs "
f"(chore(release) from '{env('PR_BRANCH')}')."
)

base = env("PR_BASE")
if base and base != "main":
ok(f"✅ pr-template not enforced on base '{base}' (main only).")
Expand Down
11 changes: 11 additions & 0 deletions .github/scripts/test_check_pr.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ def body_without(section_swap):
("linear: issue URL in body", "linear", {"PR_BODY": "https://linear.app/autonominetwork/issue/V2-719/foo"}, 0),
("linear: key in branch (lowercased)", "linear", {"PR_BRANCH": "chrisoneil/v2-720-ci-check"}, 0),
("linear: key in title", "linear", {"PR_TITLE": "AUTO-42 do the thing", "PR_BRANCH": "x"}, 0),
# --- release-promotion exemption (title prefix AND rc-* head branch) ---
("linear: release promotion exempt", "linear",
{"PR_TITLE": "chore(release): promote rc-2026.8.3", "PR_BRANCH": "rc-2026.8.3"}, 0),
("template: release promotion exempt", "template",
{"PR_TITLE": "chore(release): promote rc-2026.8.3", "PR_BRANCH": "rc-2026.8.3",
"PR_BASE": "main", "PR_BODY": "Promotes rc-2026.8.3 to release versions."}, 0),
("linear: chore(release) title without rc branch still enforced", "linear",
{"PR_TITLE": "chore(release): tidy", "PR_BRANCH": "chore/tidy"}, 1),
("template: rc branch without chore(release) title still enforced", "template",
{"PR_TITLE": "fix something", "PR_BRANCH": "rc-2026.9.1",
"PR_BASE": "main", "PR_BODY": "freeform"}, 1),
# --- pr-template: acceptances ---
("template: valid T0 body", "template", {"PR_BASE": "main", "PR_BODY": VALID_BODY}, 0),
("template: rc-* base is a no-op pass", "template", {"PR_BASE": "rc-2025.10", "PR_BODY": "anything"}, 0),
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,6 @@ description.

CI enforces the Linear link and the template fields (`linear-link` and
`pr-template` status checks); a PR that omits them fails its checks.
Release-promotion PRs — a `chore(release):` title from an `rc-*` head branch —
are exempt from both checks (they are mechanical version-bump PRs generated by
the release process).
Loading