From b67bdcf655e23e5766f306a4e09af3410b3e0614 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 13 Sep 2026 09:38:06 +0100 Subject: [PATCH 1/2] fix(ci): stamp the resolved release version onto the GitHub Packages mirror The mirror job rewrote package.json's name and registry for the GitHub Packages scope but never its version, so pnpm publish always used whatever version happens to be committed in source (the 0.0.0 placeholder, since semantic-release's own version bump lives only in the release job's ephemeral working copy and is never committed back here). Every mirror attempt since the first has failed with "cannot publish over the previously published versions: 0.0.0". Setting version from the already-resolved release tag fixes this for good. --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b83fca7..e3e6c9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -257,11 +257,12 @@ jobs: - name: Build if: steps.mirrored.outputs.already != 'true' run: pnpm build - - name: Rewrite package name and registry for the GitHub Packages scope - # GitHub Packages requires the npm package name to be scoped to the repo owner. Rewriting the fields rather than keeping a second package.json means the mirror cannot drift from the real package's metadata. publishConfig.registry must be overridden too: it takes precedence over any registry .npmrc sets, so without this the publish silently targets registry.npmjs.org instead — GITHUB_TOKEN is not a credential that registry recognises. provenance must be disabled here specifically: it is inherited from the real package's publishConfig (true, for the npmjs.com OIDC flow), but generating it requires id-token: write, which this job deliberately does not hold, and GitHub Packages has no provenance/OIDC story of its own to generate it against regardless. + - name: Rewrite package name, version, and registry for the GitHub Packages scope + # GitHub Packages requires the npm package name to be scoped to the repo owner. Rewriting the fields rather than keeping a second package.json means the mirror cannot drift from the real package's metadata. publishConfig.registry must be overridden too: it takes precedence over any registry .npmrc sets, so without this the publish silently targets registry.npmjs.org instead — GITHUB_TOKEN is not a credential that registry recognises. provenance must be disabled here specifically: it is inherited from the real package's publishConfig (true, for the npmjs.com OIDC flow), but generating it requires id-token: write, which this job deliberately does not hold, and GitHub Packages has no provenance/OIDC story of its own to generate it against regardless. version must be set explicitly too: @semantic-release/npm bumps package.json only in the release job's own ephemeral working copy, and with @semantic-release/git absent (see release.config.ts) nothing ever commits that bump back, so the tree checked out here at the release tag still carries source's committed placeholder version rather than the version the tag actually names. if: steps.mirrored.outputs.already != 'true' run: | npm pkg set name="${MIRROR_NAME}" + npm pkg set version="${{ steps.tag.outputs.version }}" npm pkg set publishConfig.registry="https://npm.pkg.github.com" npm pkg set publishConfig.provenance=false --json - name: Publish the mirror From 03ffc0b277db0860293e87ac14f7d207bdc45870 Mon Sep 17 00:00:00 2001 From: Joseph Mearman Date: Sun, 13 Sep 2026 09:39:00 +0100 Subject: [PATCH 2/2] fix(ci): poll for the SEA smoke banner instead of a fixed 5-second sleep CcPeer.start() spawns a real subprocess on Windows (WinProcInfo's own powershell.exe) before it ever binds its listening socket, and that spawn's latency is exactly what the test suite's own REAL_PROCESS_SPAWN_TEST_TIMEOUT_MS headroom exists to accommodate. A fixed 5-second background window before checking for the startup banner does not give the packaged binary the same headroom, and a real windows-11-arm runner exceeded it. Polling up to 20 seconds, and exiting the moment the banner appears or the process itself exits, also reports a genuine crash immediately rather than only after the full wait, and dumps the captured log on failure for diagnosis. --- .github/workflows/ci.yml | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3e6c9f..8309388 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,12 +196,25 @@ jobs: run: | BINARY="${{ steps.build.outputs.binary-path }}" chmod +x "$BINARY" - # The server runs until signalled; printing its startup banner within a 5s background window is the pass condition. "$BINARY" --name sea-smoke > smoke.log 2>&1 & PID=$! - sleep 5 - kill "$PID" || true - grep -q "REST facade listening" smoke.log + # Polled rather than a single fixed sleep: on Windows, start() itself spawns a real subprocess (WinProcInfo's own powershell.exe) before the server ever binds, and that spawn's latency is exactly what the test suite's own REAL_PROCESS_SPAWN_TEST_TIMEOUT_MS headroom already exists to accommodate (confirmed exceeding a naive 5s fixed wait on a real windows-11-arm runner). Exiting the loop the moment the banner appears, or the moment the process itself has died, means a healthy binary is never held to the full deadline and a genuine crash is reported immediately rather than only after the full wait. + DEADLINE=$(( $(date +%s) + 20 )) + while [ "$(date +%s)" -lt "$DEADLINE" ]; do + if grep -q "REST facade listening" smoke.log 2>/dev/null; then + kill "$PID" 2>/dev/null || true + exit 0 + fi + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + sleep 0.5 + done + kill "$PID" 2>/dev/null || true + echo "::group::smoke.log" + cat smoke.log + echo "::endgroup::" + exit 1 - name: Upload the binary as a release asset env: