Repository navigation
Conversation
The release decision moves into a tested planRelease function in .github/scripts/release.cjs. A run releases only when v<version> is not a git tag yet. latest (GHCR, npm, GitHub) moves only with a release whose version is higher than every tag. Every push to main builds edge and sha-<short>. A release/<major>.<minor> branch releases on dispatch, publishes <version> and <major>.<minor>, and uses the release-<major>.<minor> npm dist-tag when it is not the newest line. The GitHub tag now goes on the built commit. CI also runs on pull requests to release branches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G73CpTPk1EjKYeBAKPacit
Node v24.22.1 fixes CVE-2026-105482, CVE-2026-105483, CVE-2026-106069 and CVE-2026-111497 (High) and four Medium and Low CVEs: TLS hostname checks, the Brotli decoder, dns names and keep-alive sockets. engines.node now accepts only the patched 24 and 26 lines. The images build from the floating node:lts-krypton tag, which already resolves to 24.22.1. https://nodejs.org/en/blog/vulnerability/october-2026-security-releases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G73CpTPk1EjKYeBAKPacit
…d branches A release branch cut from a commit before release-branch support has the old configure and ci.yaml, so the cut-a-release playbook now takes them from main in the branch's first commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G73CpTPk1EjKYeBAKPacit
|
@thomasbeaudry this has a few impacts to existing deployments
|
thomasbeaudry
left a comment
There was a problem hiding this comment.
Found two release-channel rollback cases that should be fixed before merging. Reviewed the merge result; lint passes and the github project passes all 10 tests with 100% coverage. Full workspace coverage/e2e and real publishing were not run; local Node was 24.22.0.
If delegated to Claude Code, Fable 5 is the skill-recommended size for changes spanning release planning and publishing.
Reviewed at commit 8e83c56.
planRelease decides from a tag snapshot, so overlapping runs on two refs could both plan latest, and the one that finished last owned it, or they could release one version from two commits. The concurrency group now spans every ref and never cancels a running release. planRelease also refuses an untagged version lower than a tag on its own line, so a late retry cannot move the line tag back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G73CpTPk1EjKYeBAKPacit
| echo "Publishing ${name}@${version}" | ||
| pnpm --filter "${name}" publish --no-git-checks | ||
| echo "Publishing ${name}@${version} under ${NPM_TAG}" | ||
| pnpm --filter "${name}" publish --no-git-checks --tag "${NPM_TAG}" |
There was a problem hiding this comment.
[P2] Reconcile the selected dist-tag for already-published npm versions
The new npmTag can change between whole-run retries, but --tag is only applied after the existing-version check has continued past published packages. For example, 2.5.3 can publish its packages under latest and then fail before creating v2.5.3. If 2.6.0 succeeds before the whole 2.5.3 run is retried, the retry correctly plans npmTag=release-2.5; however, every already-published 2.5.3 package is skipped, so release-2.5 is never created or advanced for those packages. The workflow can finish green while installs using that maintenance channel fail or get an older version. I reproduced this with the current planRelease and the exact Publish Packages shell block: exit 0, only npm view invoked, no channel update. Please reconcile or verify the planned dist-tag even when the tarball exists, or fail explicitly and require a fresh patch, and cover this retry scenario. See npm dist-tag documentation for the distinction between publishing and tagging an existing version.
Reviewed at commit a9a35d7.
There was a problem hiding this comment.
Fixed in 68aa1e3, with the explicit failure. A reconcile with npm dist-tag add would need the opt-in "Allow npm dist-tag" setting on all six trusted publishers, because OIDC trusted publishing cannot move a dist-tag otherwise.
The shell loop is now .github/scripts/publish-npm.cjs, run through actions/github-script. It skips a published version only while the planned npmTag points at it. Otherwise it throws:
@odc/a@2.5.3 is already published, but its release-2.5 dist-tag is on '2.5.2'; trusted publishing cannot move a dist-tag, so cut a fresh patch
Your scenario (2.5.3 published under latest, then 2.6.0 releases, then a whole-run retry of 2.5.3 that plans release-2.5) is the test should fail a re-run whose planned dist-tag is elsewhere, so the channel is never left behind silently in .github/scripts/__tests__/publish-npm.test.ts. The normal re-run, where the tag still points at the version, is covered by should skip a published version whose planned dist-tag already points at it. 15 tests in the github project, and both scripts have 100% coverage. The docs (release-channels.md, cut-a-release.md, the odc-release skill) state the rule and the recovery.
A re-run planned after a newer release can carry another npmTag than the run that published. The publish loop skipped every published version, so the release-<major>.<minor> channel stayed on an older version while the run finished green. Trusted publishing cannot move a dist-tag, so the new tested .github/scripts/publish-npm.cjs skips a published version only while npmTag points at it, and otherwise fails with a request for a fresh patch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G73CpTPk1EjKYeBAKPacit
Why
The October 2026 Node.js security releases fix four High CVEs in Node 24 (TLS hostname checks, the Brotli decoder,
dns), and the api and gateway images run Node. The publishedlatestand2.5.2images were built on 2026-10-07 from432dec8with Node 24.21.0, so they are vulnerable.Those images are also not the code at the
v2.5.2tag (3c3f081): every push tomainre-pushed2.5.2andlatest. This PR changes that, so that we can shipv2.5.3from arelease/2.5branch built from the code that deployments run now.What changed
ci:release branches, andlatestonly on tagged releases.github/scripts/release.cjsnow exports a pureplanRelease({ ref, tags, version }). A run is a release only whenv<version>is not a git tag. It reads git tags only, never GHCR or npm.mainpushesedgeandsha-<7>. A release pushes<version>and<major>.<minor>, and alsolatest(GHCR, npm dist-tag, GitHub "latest release") only when its version is higher than every tag.release/<major>.<minor>branches release onworkflow_dispatchonly. On a branch that is not the newest line, npm publishes under dist-tagrelease-<major>.<minor>.configurefails on another ref, on a version that is already tagged, or on a version that is not on the branch's line.target_commitish: ${{ github.sha }}. Before this change, a new tag was created on the default branch.ci.yamlalso runs on PRs torelease/**..agents/docs/architecture/release-channels.md.cut-a-release.mdgets a "From a release branch" section. Theodc-releaseandodc-testingskills,testing-strategy.md,.env.templateand the playground deployment doc are updated.fix:Node 24.22.1engines.nodeis^24.22.1 || >=26.12.1. This is stricter than a new minimum: it also refuses the vulnerable 26.x before 26.12.1. pnpm only warns, becauseengine-strictis off.mainkeepsFROM node:lts-krypton, which already resolves to 24.22.1, so the next build is patched. A release branch pinsFROM node:<exact>(documented in the playbook and inrelease-channels.md).After merge
The merge run pushes
edgeonly, becausev2.5.2exists, solatestdoes not change. Next: addrelease/*to thereleaseenvironment's deployment-branch policy, cutrelease/2.5, pin Node, bump to 2.5.3, and dispatch. That dispatch is the first run ofpublish-npm --tag,make_latestandtarget_commitish.Verification
Run locally on Node 24.22.1:
pnpm lint: passes.pnpm test:coverage: passes at 100%. New vitest projectgithubwith.github/scripts/__tests__/release.test.ts(10 tests;release.cjshas 100% statement and branch coverage).pnpm test:e2e: 276 of 278 pass. The two failures are not related to this change:record-file-deletion.spec.tsgets a 503 because the local.envhasSTORAGE_ENABLED=false, andsubject-detail.spec.tstimed out on a detached element, then passed 9 of 9 on a second run../scripts/increment-version.ts --bump patch --yesdry run: the 2.5.3 changelog section generates from all 321 commits sincev2.5.2(I undid the dry run).Exceptions:
release/2.5dispatch.excludeentry'**/scripts/**'is narrowed to'{apps,packages,runtime}/*/scripts/**', because it also matched.github/scripts/. No file in the earlier coverage scope is under ascripts/directory.workspace-map.mdgets no row for thegithubproject, because.githubis not a workspace.Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com