From 00e97d02a3cc61ee78848c2a289fc268c785cba6 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sat, 10 Oct 2026 15:45:40 +0100 Subject: [PATCH] fix: let the release pull request receive its required checks GitHub skips push and pull_request workflows for a head commit whose message carries its skip-workflow token, so the default release commit message kept the staged pull request from ever reporting a required check. The default no longer carries the token. A release branch pushed with GITHUB_TOKEN also starts no workflow. This repository's own staging job now checks out with a deploy key so the push runs CI, CI runs on release/** pushes, and the job skips the push that lands the merged release commit. --- .github/workflows/ci.yml | 7 +++++-- README.md | 4 ++-- src/plugin.ts | 2 +- src/plugin.unit.test.ts | 4 ++-- src/types.ts | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0901131..8e3a177 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main] + branches: [main, "release/**"] pull_request: types: [opened, synchronize, reopened, closed] workflow_dispatch: @@ -92,7 +92,8 @@ jobs: stage-release: name: Stage release needs: [commitlint, lint, typecheck, test] - if: github.ref == 'refs/heads/main' && github.event_name == 'push' + # The merged release commit lands on main as a push of its own; staging again from it would open a release for the release. + if: github.ref == 'refs/heads/main' && github.event_name == 'push' && !startsWith(github.event.head_commit.message, 'chore(release):') runs-on: ubuntu-latest timeout-minutes: 15 permissions: @@ -104,6 +105,8 @@ jobs: with: # semantic-release analyses the full commit history since the last release. fetch-depth: 0 + # Pushes made with GITHUB_TOKEN never start workflows, so the release branch is pushed over SSH with a deploy key and its CI runs like any other branch's. + ssh-key: ${{ secrets.RELEASE_DEPLOY_KEY }} - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v7 with: diff --git a/README.md b/README.md index 4131d8d..ced2296 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ const config: Options = { export default config; ``` -Requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable with permission to push a branch and open pull requests on the repository (the default `GITHUB_TOKEN` in GitHub Actions is sufficient, since the pushed branch is never the protected one). +Requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable with permission to push a branch and open pull requests on the repository (the default `GITHUB_TOKEN` in GitHub Actions is sufficient to push and open the pull request, since the pushed branch is never the protected one). GitHub does not start workflows for pushes made with that token, so a repository that requires status checks on the release pull request pushes the branch with a deploy key instead (`ssh-key` on `actions/checkout`) and runs CI on `release/**` pushes. ## Options @@ -47,7 +47,7 @@ Requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable with permission to | --------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | | `assets` | `["CHANGELOG.md", "package.json"]` | Files to stage into the release commit. | | `branch` | `"release/${version}"` | The branch pushed to and PR'd from. `${version}` is replaced with the release version. | -| `commitMessage` | `"chore(release): ${version} [skip ci]"` | The commit message and PR title. `${version}` and `${notes}` are both available. | +| `commitMessage` | `"chore(release): ${version}"` | The commit message and PR title. `${version}` and `${notes}` are both available. | | `labels` | `[]` | Labels applied when a new pull request is opened. Never removed from an already-open one. | ## License diff --git a/src/plugin.ts b/src/plugin.ts index cb63b7a..9f122a4 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -6,7 +6,7 @@ import type { PluginConfig, PrepareContext } from "./types.js"; const DEFAULT_ASSETS = ["CHANGELOG.md", "package.json"]; const DEFAULT_BRANCH = "release/${version}"; -const DEFAULT_COMMIT_MESSAGE = "chore(release): ${version} [skip ci]"; +const DEFAULT_COMMIT_MESSAGE = "chore(release): ${version}"; function readGithubToken(env: PrepareContext["env"]): string | undefined { const token = env["GITHUB_TOKEN"] ?? env["GH_TOKEN"]; diff --git a/src/plugin.unit.test.ts b/src/plugin.unit.test.ts index e5406de..c34e71e 100644 --- a/src/plugin.unit.test.ts +++ b/src/plugin.unit.test.ts @@ -71,12 +71,12 @@ describe("prepare", () => { expect(gitOperations.checkoutNewBranch).toHaveBeenCalledWith("release/1.2.3"); expect(gitOperations.stage).toHaveBeenCalledWith(["CHANGELOG.md", "package.json"]); - expect(gitOperations.commit).toHaveBeenCalledWith("chore(release): 1.2.3 [skip ci]"); + expect(gitOperations.commit).toHaveBeenCalledWith("chore(release): 1.2.3"); expect(gitOperations.push).toHaveBeenCalledWith("release/1.2.3"); expect(vi.mocked(pullRequestOperations.openOrUpdate)).toHaveBeenCalledWith({ branch: "release/1.2.3", baseBranch: "main", - title: "chore(release): 1.2.3 [skip ci]", + title: "chore(release): 1.2.3", body: context.nextRelease.notes, labels: [], }); diff --git a/src/types.ts b/src/types.ts index 8bd52a7..72ae694 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,7 +11,7 @@ export interface PluginConfig { */ readonly branch?: string; /** - * The release commit's message, and the pull request's title. `${version}` and `${notes}` are replaced with `nextRelease.version` and `nextRelease.notes`. Defaults to `"chore(release): ${version} [skip ci]"`. + * The release commit's message, and the pull request's title. `${version}` and `${notes}` are replaced with `nextRelease.version` and `nextRelease.notes`. Defaults to `"chore(release): ${version}"`. Avoid `[skip ci]`: GitHub skips `push` and `pull_request` workflows for a head commit that contains it, so the release pull request would never receive its required checks. */ readonly commitMessage?: string; /**