From b8ca42bed8f16dc70ccfe5a88af562e9ce043b43 Mon Sep 17 00:00:00 2001 From: Michael I Chen Date: Tue, 15 Sep 2026 17:37:50 -0700 Subject: [PATCH] ci(release-pr): move the PR body to a file Every release PR has shipped hard-wrapped prose, because the body was an inline YAML block and yamllint caps this file at 100 columns. The two rules were in direct conflict: the wrapping that keeps the workflow readable is what GitHub renders as a
in a pull request body, so the release PR reached its reader ragged. Exempting release-pr.yml from the column rule would trade a real lint on a 340-line release-critical workflow for prose formatting. A file gives up nothing: `body-path` takes precedence over `body`, and the body becomes a Markdown document that prettier, markdownlint and the unwrap hook already run over -- the same tooling the prose is being fixed to satisfy. The tag is substituted rather than interpolated, since `body-path` reads the file literally. It has already passed parse-version.sh, so it is three dot-separated runs of digits and carries no sed metacharacter. MD041 wants a top-level heading on line 1, which is a whole-document rule applied to a fragment. Handled the way cliff.toml already handles its own header: a markdownlint-configure-file directive, dropped at render time so lint scaffolding does not reach the pull request. Also corrects the first bullet. It claimed the run refreshes the Unreleased section; stamp-changelog.sh stamps the pending version as its own section, and the difference between those two is what this workflow's own comments exist to explain. Co-Authored-By: Claude Opus 5 (1M context) --- .github/release-pr-body.md | 13 ++++++++++ .github/workflows/release-pr.yml | 44 ++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 .github/release-pr-body.md diff --git a/.github/release-pr-body.md b/.github/release-pr-body.md new file mode 100644 index 0000000..350418b --- /dev/null +++ b/.github/release-pr-body.md @@ -0,0 +1,13 @@ + + +This PR prepares release `@RELEASE_TAG@`. + +## Included + +- Stamps `@RELEASE_TAG@` into `CHANGELOG.md` as its own section +- Points the `README.md` install snippet at `@RELEASE_TAG@` +- Validates tests before proposing release prep + +The `.pre-commit-config.yaml` self-pin is not touched here — the tag does not exist yet, so pinning it would fail this PR's own checks. The weekly pre-commit autoupdate moves it forward after the tag lands. + +Merging this PR creates and pushes the signed tag `@RELEASE_TAG@` and publishes the release. No local tagging step is needed. diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index 2cfc5f7..5018ac5 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -267,6 +267,33 @@ jobs: - name: Run tests run: make test + # The body is a Markdown file rather than an inline block, and the reason + # is that the two rules were in direct conflict. Inline, yamllint's + # 100-column limit forces every paragraph to be wrapped -- and GitHub + # renders each of those newlines in a pull request body as a
, so the + # rule keeping this workflow readable was reaching every release PR as + # ragged prose. As a file it is a document the repository's own tooling + # already lints: prettier, markdownlint and the unwrap hook all run on it. + # + # Substituted rather than interpolated, because `body-path` reads the file + # literally. The tag has already been through parse-version.sh, so it is + # three dot-separated runs of digits and carries no sed metacharacter. + # + # The markdownlint directive is dropped here rather than shipped: MD041 is + # a whole-document rule and that file is a fragment, so the directive is + # lint scaffolding, not content. The blank line it leaves goes with it. + - name: Render the release PR body + id: body + env: + RELEASE_TAG: ${{ needs.resolve.outputs.tag }} + run: | + set -euo pipefail + rendered="$RUNNER_TEMP/release-pr-body.md" + sed "s/@RELEASE_TAG@/${RELEASE_TAG}/g" .github/release-pr-body.md | + sed '/^