Skip to content

ci(release): lock the release toolchain - #29

Merged
maciejwitowski merged 3 commits into
mainfrom
maciej/pin-release-tooling
Sep 24, 2026
Merged

maciejwitowski merged 3 commits into
mainfrom
maciej/pin-release-tooling

Conversation

@maciejwitowski

@maciejwitowski maciejwitowski commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The Release workflow fails at generateNotes on every push that should cut a release. No GitHub release has been published since v2.3.1 (2026-08-03), so #27, #26 and #28 are on main with no release. And v2.3.1 itself shipped with an empty body.

Run Commit Result
2026-08-03 ffd802d #21 success, but v2.3.1 notes are the version header only
2026-08-12 → 08-19 chore/ci commits success (no release due, notes step never ran)
2026-09-04 a4cb188 #27 failure
2026-09-14 a409872 #26 failure
2026-09-14 47db176 #28 failure
Failed step "generateNotes" of plugin "@semantic-release/release-notes-generator"
Error: Missing helper: "conventional-changelog-conventionalcommits requires conventional-changelog-writer@9 or newer ..."

Cause

The workflow installed the release tooling unpinned, with no lockfile:

  • npx semantic-release resolves to 25.0.9. Its @semantic-release/release-notes-generator 14.1.1 (the latest) depends on conventional-changelog-writer ^8.
  • npm install --no-save conventional-changelog-conventionalcommits resolves to the latest preset. Preset v10 targets writer 9:
    • 10.0.0–10.3.0 (from 2026-06-26): no error, but writer 8 renders only the version header. That is why the v2.3.1 release has no notes, while v2.3.0 (January, preset 9.x) does.
    • 10.4.0 (2026-08-18): added an explicit guard, which turned the silent empty output into the hard failure above.

Pinning only the top-level packages does not close this. semantic-release loads its plugins from its own node_modules tree, and every range below it (release-notes-generator ^14.1.0, conventional-changelog-writer ^8.0.0, the GitHub plugin's Octokit stack) resolves fresh on each run. The job holds a contents: write token, so any new npm release in that tree runs with it, unreviewed.

Fix

  • New .github/release/package.json + committed package-lock.json for the toolchain:

    Package Version
    semantic-release 25.0.9
    @semantic-release/commit-analyzer 13.0.1
    @semantic-release/release-notes-generator 14.1.1
    @semantic-release/github 12.0.9
    conventional-changelog-conventionalcommits 9.3.1, the last major built for writer 8
  • The job installs it with npm ci --ignore-scripts --prefix .github/release (no package in the tree has an install script) and runs .github/release/node_modules/.bin/semantic-release from the repo root, so .releaserc.yaml is still the config.

  • It lives under .github/ so the operator checkout gets no root package.json. .gitignore covers its node_modules.

  • Node pinned to 24, the version this was verified on.

  • A comment in the workflow says when to bump the preset: once the notes generator ships writer 9.

Verification

Ran the two workflow steps as written, on Node 24 / npm 11, on a checkout of main at 47db176 with no root node_modules, the full .releaserc.yaml and a GitHub token, --dry-run --no-ci:

Preset comparison (same command pair, earlier run):

Preset Result
9.3.1 notes render Bug Fixes
10.3.0 notes are the header only
10.4.0 fails with the same "Missing helper" error as CI

Cosmetic: #26's commit body contains the word "closes" before "compatibility", so its line gets a bogus closes #compatibility link. Harmless; editing the release body after publish fixes it if wanted.

Heads-up on merging

Merging this triggers the workflow on main, which will publish v2.3.2 as a public GitHub release containing #26, #27 and #28 (Prysm v7 config keys, the deposit contract switch config, the validator REST fix). The GitHub plugin also comments "included in version 2.3.2" on those PRs and labels them released, as it did on #21 for v2.3.1. Anyone watching this repo's releases gets notified, so merge when that announcement is fine to go out.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dqz4KWjLzxNEJuea75Avb7

maciejwitowski and others added 3 commits September 14, 2026 13:17
The Release workflow has failed at generateNotes on every release-bearing
push since 2026-09-04 (a4cb188, a409872, 47db176), so v2.3.2 was never
published.

conventional-changelog-conventionalcommits 10.4.0 requires
conventional-changelog-writer 9. @semantic-release/release-notes-generator
14.1.1, the latest, still depends on writer ^8. The workflow installed the
preset and ran semantic-release unpinned, so it picked up the incompatible
pair.

Pin the preset to 10.3.0 and semantic-release to 25.0.9. Verified with
`semantic-release --dry-run` against main: 10.3.0 completes generateNotes
for 2.3.2, 10.4.0 fails with the same "Missing helper" error as CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c4dMZ2kYs4E65ysXHH4Qz
…er 8

Preset 10.3.0 avoids the "Missing helper" error but emits writer-9 template
options, so release-notes-generator 14.1.1 (writer ^8) renders only the
version header. v2.3.1 shipped with exactly that empty body. 9.3.1 renders
the Bug Fixes section in the same format as v2.3.0.

Verified with `semantic-release@25.0.9 --dry-run` on main at 47db176.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c4dMZ2kYs4E65ysXHH4Qz
`npx semantic-release@25.0.9` pinned only the top package. semantic-release
loads its plugins from its own node_modules tree, and every dependency
below it (release-notes-generator ^14.1.0, conventional-changelog-writer
^8.0.0, the GitHub plugin's Octokit stack) resolved fresh on each run with
no lockfile. Any new npm release in that tree reached a job that holds a
contents:write token, unreviewed.

Move the toolchain into .github/release/package.json with a committed
package-lock.json: semantic-release 25.0.9, commit-analyzer 13.0.1,
release-notes-generator 14.1.1, github 12.0.9, conventionalcommits preset
9.3.1. The job installs it with `npm ci --ignore-scripts` (no package in the
tree has an install script) and runs that binary from the repo root. It
lives under .github/ so the operator checkout gets no root package.json.
Node is pinned to 24, the version this was verified on.

Verified on Node 24 against main at 47db176, running the two workflow steps
as written with the full .releaserc and a GitHub token, `--dry-run --no-ci`,
no root node_modules: next version 2.3.2, notes render Bug Fixes with #26,
#27 and #28.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dqz4KWjLzxNEJuea75Avb7
@maciejwitowski maciejwitowski changed the title ci(release): pin semantic-release and the conventionalcommits preset ci(release): lock the release toolchain Sep 14, 2026
@maciejwitowski
maciejwitowski merged commit 01abe45 into main Sep 24, 2026
4 checks passed
@maciejwitowski
maciejwitowski deleted the maciej/pin-release-tooling branch September 24, 2026 10:15
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.4.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants