Skip to content

ci: check the pull request title, which a squash merge turns into a commit - #50

Merged
sgjholt merged 1 commit into
mainfrom
claude/roadmap-priorities-pe63kc
Sep 26, 2026
Merged

sgjholt merged 1 commit into
mainfrom
claude/roadmap-priorities-pe63kc

Conversation

@sgjholt

@sgjholt sgjholt commented Sep 21, 2026 •

Copy link
Copy Markdown
Owner

Closes the hole that cost v0.5.0 its changelog entry. Its own title is the first thing the check runs against.

Why a hook cannot do this

A squash merge discards every subject on the branch and uses the PR title instead, composed by GitHub server-side, after all local hooks have run. The commit-msg hook never sees the subject that lands on main — and that subject is exactly what release-please parses.

#45 is the cost:

commit could not be parsed: 2af57e8 Make the station selection mean what the
  config says: radius, and one instrument per station (#45)
error message: Error: unexpected token ' ' at 1:5, valid tokens [(, !, :]
commits: 0
✔ Considering: 0 commits
✔ No commits for path: ., skipping

A radius fix, a WGS84 cut and two new config fields went out with no bump and no changelog entry. Both had to be repaired by hand in #47.

What this adds

.github/workflows/pr-title.yml runs tools/check_pr_title.py on opened, edited and reopened.

Written as a script rather than pulling in amannn/action-semantic-pull-request, for one reason worth the extra file: the accepted types are the ones release-please-config.json declares, and a test asserts the two lists are identical. A generic action would happily accept a type the release config does not know — which lands the work in no changelog section, the same silent failure reached from the other side.

The failure message names one thing to change:

::error::The pull request title is not a Conventional Commit: the type 'Feat'
must be lower case. A squash merge makes this title the commit subject on main,
and release-please computes the version and the changelog from it, so an
unparseable title ships the work with no changelog entry. Known types: feat,
fix, perf, refactor, docs, build, revert, test, ci, style, chore. Example:
'fix(acquire): ask for waveforms by name'.

Not "does not match ^(feat|fix|…)(\(.*\))?!?: .+$".

Security

The title is text anyone who can open a PR chooses. It reaches the script through env:, never interpolated into a shell command, and the workflow is pull_request with permissions: {} — not pull_request_target, which would hand a write token to a fork's title.

Tests

29 cases in tests/test_pr_title.py, seeded from what actually happened rather than from the grammar: #45's real title, #46's original Roadmap: …, a capitalised type, a missing space, an empty scope, every known type, and the (#45) suffix a squash appends.

Verification

pytest tests/test_pr_title.py tests/test_tools_track_the_api.py   38 passed
ruff check / ruff format --check                                  clean
mypy                                                              no issues in 65 source files

All 14 checks green on 3b4db69, including pr-title passing on this PR's own title.

Two things this does not do

The estimator default change is not in here — measuring it turned up 21 failing tests and a prerequisite (REFACTOR_PLAN §6.6's absent config pin), so it wants its own PR rather than riding along with a CI fix.

🤖 Generated with Claude Code

…ommit

A squash merge discards every subject on the branch and uses the pull request
title instead, composed by GitHub after all local hooks have run. So the
`commit-msg` hook cannot see the subject that actually lands on `main`, and
`release-please` parses exactly that subject.

#45 is what this costs. Squash-merged as "Make the station selection mean what
the config says: radius, and one instrument per station (#45)", the release job
recorded:

    commit could not be parsed: 2af57e8 Make the station selection mean ...
    error message: Error: unexpected token ' ' at 1:5, valid tokens [(, !, :]
    Considering: 0 commits
    No commits for path: ., skipping

A radius fix, a WGS84 cut and two new configuration fields went out with no
version bump and no changelog entry; both had to be repaired by hand in the
release pull request afterwards.

Written as a script rather than a third-party action so the accepted types are
the ones `release-please-config.json` declares — a test asserts the two lists
are identical, since a type this accepts but the release configuration does not
know lands in no changelog section, which is the same silent failure reached
from the other side. The failure message names one thing to change rather than
printing the grammar.

The title is attacker-chosen text, so it reaches the script through the
environment, never interpolated into a shell command, and the workflow runs on
`pull_request` with no permissions rather than `pull_request_target`.

Merging with a merge commit remains the safer default and keeps the individual
subjects; this makes a squash safe when one is used anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 SpecMod | 🛠️ Build #34676347 | 📁 Comparing 3b4db69 against latest (ed4e10c)

  🔍 Preview build  

2 files changed
± roadmap.html
± tutorial/specmod-tutorial.html

@sgjholt
sgjholt marked this pull request as ready for review September 26, 2026 17:44
@sgjholt
sgjholt merged commit 74f1bb9 into main Sep 26, 2026
16 checks passed
@sgjholt
sgjholt deleted the claude/roadmap-priorities-pe63kc branch September 26, 2026 17:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants