[fix] release: stop the snapshot job starving the release job
Publish snapshot / snapshot (push) Successful in 1m5s

Pushing a release pushes the branch and then the tags seconds apart.
publish-snapshot.yml keys its concurrency group on the branch and
release.yml keys its own on the tag, so the two never gate each other —
on a single runner they race for it and the branch push always wins.

On 1.0.1 the snapshot job wedged extracting a layer of the runner image,
the release job never started, release.mjs gave up after its 20-minute
wait, and two of three tags were left unpushed. The report still printed
a bold "Done" above an empty shipped list, so it read as a success.

- publish-snapshot.yml skips commits whose message starts with "release:".
  A snapshot of a release commit is the same tree the tag is about to
  publish properly, so skipping costs nothing and removes the race.
- waitForRelease() offers to keep waiting instead of giving up. No
  timeout value survives a wedged runner, so the real choice is between
  asking and making the operator finish the release by hand. --yes and a
  non-interactive run still give up; the latter matters because confirm()
  answers with its default without a terminal, which would extend the
  deadline forever.
- The final header says "Blocked" when it is, and labels the packages
  that did ship before the blockage.
- --wait-timeout defaults to 2400s rather than 1200s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCzYTAm9QUhvLNr2EpdagJ
This commit is contained in:
Benjamin Diedrichsen
2026-09-01 15:28:12 +02:00
co-authored by Claude Opus 5
parent eac44b637e
commit 2810d4491f
3 changed files with 73 additions and 14 deletions
+24 -6
View File
@@ -342,12 +342,15 @@ regardless of `--tag`; on Gitea it did not exist at all. Note that `npm view
<name>` against a registry with no `latest` tag prints nothing and exits **0**,
which is why this looked like a working lookup. (`npm view <name>@<version>`
does exit 1 for a missing version, so the workflows' idempotency guards are
fine.) All four packages were reset to `0.5.0`; `1.0.0-alpha5` stays the
numerically highest version on npmjs, so install with an explicit `@latest`.
fine.) All four packages were reset to `0.5.0`, and `nopy`, `nopy-cubes` and
`nopy-cubes-core` have since gone out as `1.0.1` — the first release where
`latest` actually moved on both registries. `keyman` is still `0.7.0` and on
neither.
- Push to `main` → `publish-snapshot.yml` publishes every package to the Gitea
registry as `<version>-main.<run>.g<sha>` under the `main` dist-tag. The
version is set on the runner with `npm pkg set` and never committed.
version is set on the runner with `npm pkg set` and never committed. It skips
commits whose message starts with `release:` — see *Runner contention* below.
- `git tag <dir>-v<version>` (e.g. `nopy-v1.2.0` — the directory under
`packages/`, not the npm name) → `release.yml` publishes to Gitea *and* npmjs.
The tag chooses the package, `package.json` supplies the version, and the run
@@ -400,12 +403,27 @@ gets `-vvv --debug`. Treat `docs/REFACTORING.md` as a plan, not a record.
The publish lane has now run against the Gitea registry: all four packages are
there under `@main`, and `pnpm run try:snapshot` installs them into a throwaway
project with npm and runs the binary. The npmjs lane has only ever published
`@bitsquare/nopy`; `keyman`, `nopy-cubes` and `nopy-cubes-core` have never been
released there. That used to be caught by the *check linked deps are released*
project with npm and runs the binary. The npmjs lane has published `nopy`,
`nopy-cubes` and `nopy-cubes-core` at `1.0.1`; `keyman` has never been released
there. Ordering used to be enforced by the *check linked deps are released*
guard in `release.yml`; now it is `pnpm run release` that holds `nopy`'s tag back
until `nopy-cubes` answers on npmjs.
### Runner contention
`scripts/release.mjs` pushes the branch and then the tags seconds apart.
`publish-snapshot.yml` keys its concurrency group on the branch and `release.yml`
keys its own on the tag, so the two workflows never gate each other — on a
single runner they simply race for it, and the branch push always wins. The
1.0.1 release is what surfaced this: the snapshot job wedged extracting a layer
of `runner-images:ubuntu-latest`, the release job never started, `release.mjs`
gave up after its 20-minute wait, and two of the three tags were left unpushed
while the report still printed a bold **Done**. Three things changed as a
result — the snapshot job skips `release:` commits, the wait offers to keep
waiting rather than giving up (no timeout survives a wedged runner), and the
final header says **Blocked** when it is. The tags were pushed by hand
afterwards; all three packages are on npmjs.
Nothing checks that a bundle and the CLI reading it are compatible versions;
`nopy.engines` was considered and deferred. `docs/CUBE-PACKAGES.md` is where all
of this came from and is now a record of what was built, including what differed