commit-guard: read the PR's commits, not just its title, on the standing lines

Open
#3,199 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
git, github-actions, shell
Domain
ci-cd, devops, release

Research direction

Start with the routing job that currently classifies PR_TITLE and PR_BODY, then read ADR 0004 §1 and the related AGENTS.md guidance. Trace how standing-line PRs are identified and how existing exemptions are applied. Done means the job also checks PR commits for breaking Conventional Commit subjects or bodies without changing the required merge behavior, with a Common failures entry added to AGENTS.md.

Written by the indexing model from the issue text.

Description

github_actions tooling

commit-guard classifies the PR title and body only. On next that is not what lerna reads, so a ! inside a branch commit opened a major release line unnoticed.

What happened

2026-09-18: PR #3166 merged into next as a true merge commit, so its 23 branch commits entered next's history — among them refactor(CoachMark)!: compose the dismiss action instead of labelling it. The publish run derived the bump from those commits (lerna version --conventional-commits --conventional-prerelease="*"), read the ! as a premajor, and published 2.0.0-next.0 for all 13 packages under dist-tag next — instead of 1.2.0-next.62.

The change broke nothing for consumers: CoachMark did not exist in 1.2.0-next.61, so the props it removed had never shipped. What it did break is ADR 0004: a breaking change belongs on the major line, next is the X.Y.0-next.N minor line, and a promotion would have graduated main to 2.0.0.

Why the guard missed it

The routing job classifies PR_TITLE and PR_BODY:

if printf '%s' "$PR_TITLE" | grep -qE '^[a-z]+(\([^)]+\))?!:'; then is_breaking=1; fi
if printf '%s\n' "$PR_BODY" | grep -qE '^[[:space:]]*BREAKING(-| )CHANGE:'; then is_breaking=1; fi

The title was feat(CoachMark): add a self-opening hint that points at a control — clean, routed to next, guard green. The workflow's own comment states the assumption:

Because this repo squash-merges (the PR title becomes the commit subject), the PR title is what must be a valid Conventional Commit, so that is what we lint.

That assumption holds for main and not for next. The last five PRs into main are squash commits (one parent); the last eight into next are merge commits (two parents). Merge commits are enabled repo-wide (allow_merge_commit: true), and ADR 0004 §1 actively requires them for the sync PRs.

Why it is invisible in the log

The repo sets merge_commit_title: PR_TITLE and merge_commit_message: BLANK. A merge commit therefore carries the same subject shape as a squash — PR title plus (#1234), empty body — instead of GitHub's default Merge pull request #1234 from …. In git log --oneline the two are indistinguishable; only %p or --graph shows the second parent.

Proposed fix

Extend the routing job: when the base is a standing line, scan the PR's commits in addition to the title.

  • gh api repos/{owner}/{repo}/pulls/{n}/commits --paginate --jq '.[].commit.message'
  • flag a subject matching ^[a-z]+(\([^)]+\))?!: or a body line matching ^BREAKING[ -]CHANGE:
  • keep the existing exemptions (next, N.x / next-major, sync/*, release/* heads) — promotion and sync sources legitimately carry breaking commits
  • the job already triggers on synchronize, so a commit pushed later is caught

Alternative considered and rejected as a blanket rule: squash-only into next. It would fix the class of problem but breaks the superset invariant for the sync PRs (ADR 0004 §1), which must stay true merges.

Worth adding alongside: a Common failures entry in AGENTS.md, since the symptom (an unexpected major on next) points nowhere near the cause.

Cleanup already done

  • next force-pushed without the !, release commit dropped (75b21ea1d, 1.2.0-next.62); the rebuilt merge has a tree identical to the old one
  • tag 2.0.0-next.0 deleted, 1.2.0-next.62 published, dist-tag next back on the 1.2 line
  • #3198 (forward-merge sync) rebuilt on the rewritten next
  • #3179 carries the old ! commit in its ancestry and still needs handling
Dominant language
TypeScript
Stars
15
Forks
3
Avg merge
1d 17h
Merged PRs (30d)
184

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from mittwald/flow

All issues in mittwald/flow

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.