From 8c93c606e086185997a39a29d0806a6585720141 Mon Sep 17 00:00:00 2001 From: prosdev Date: Tue, 14 Jul 2026 22:09:51 -0700 Subject: [PATCH] fix(release): drop setup-node registry-url so OIDC npm info read isn't poisoned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Symptom After the pnpm 10 fix, `changeset publish` still fails: it reports every already-published package (dev-agent, kero) as "has not been published on npm", tries to republish, and npm 11 rejects with "cannot publish over the previously published versions". dev-agent published once (new version) then every subsequent run is red. ## Root cause (grounded) 1. `changeset publish` runs `npm info --json` to detect existing versions. npm's `--json` writes errors to stderr and leaves stdout EMPTY on any non-200 (npm/cli#5286, #5444). changesets treats empty stdout as E404 -> "not published" (intentional, for GitHub Packages — changesets#1098), so it tries to republish. 2. `actions/setup-node` with `registry-url` writes an .npmrc line `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}` (setup-node authutil.ts). Under OIDC there is no real token, so the `npm info` READ carries a bad/placeholder token -> 401 -> empty stdout -> false "unpublished". The PUBLISH still works because npm uses a separate OIDC token-exchange endpoint — hence the read-fails-but-publish-succeeds asymmetry. 3. Why it surfaced now: npm 11 added eager preflight rejection of "cannot publish over the previously published version" (npm commit 31455b2, referenced in changesets' own source). Older npm was lax; `npm install -g npm@latest` drifting to npm 11 exposed the latent read bug. ## Fix Remove `registry-url` from setup-node. With no auth .npmrc, the pre-publish `npm info` read goes out anonymously -> 200 -> changesets sees the real published versions and correctly SKIPS them. OIDC trusted publishing is unaffected: npm handles publish auth via its token-exchange endpoint, and publishConfig.registry (added earlier) targets npmjs.org. This is the canonical documented OIDC-only setup (npm trusted-publishing docs; changesets/action >=1.7.0 writes its own OIDC-clean .npmrc). Also adds a temporary non-fatal "Diagnose npm info read" step that prints whether the read returns data, to confirm the fix on the next run. Remove once green. Refs: npm/cli#5286, #8678, #8976; changesets#1098; pnpm#11526; actions/setup-node#1440; changesets/action#545. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ae05c42..65c9eaf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,12 +31,40 @@ jobs: with: node-version: 22.x cache: 'pnpm' - registry-url: 'https://registry.npmjs.org' + # NOTE: intentionally NO `registry-url`. Setting it makes setup-node + # write an .npmrc line `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}`. + # Under OIDC there is no real token, so `changeset publish`'s pre-publish + # `npm info --json` read carries a bad/placeholder token -> 401 -> + # empty stdout, which changesets treats as "not published" and tries to + # republish, and npm 11 hard-fails with "cannot publish over the previously + # published versions". Dropping registry-url makes the read anonymous (200). + # OIDC trusted publishing still works: npm handles publish auth via its + # token-exchange endpoint, and publishConfig.registry targets npmjs.org. - # Ensure npm 11.5.1+ for trusted publishing support + # Ensure npm 11.5.1+ for trusted publishing support (OIDC needs >= 11.5.1) - name: Update npm run: npm install -g npm@latest + # Diagnostic (non-fatal): prove the pre-publish read is anonymous and returns + # data. If stdout is non-empty JSON here, changesets will correctly SKIP + # already-published packages instead of re-publishing them. Remove once green. + - name: Diagnose npm info read + continue-on-error: true + run: | + echo "== npm config: registry & userconfig ==" + npm config get registry + echo "userconfig=${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}" + echo "== .npmrc (auth lines masked) ==" + cat "${NPM_CONFIG_USERCONFIG:-$HOME/.npmrc}" 2>/dev/null | sed -E 's/(_authToken|_auth|_password)=.*/\1=/' || echo "(no .npmrc)" + for pkg in @prosdevlab/dev-agent @prosdevlab/kero; do + out=$(npm info "$pkg" --json 2>/dev/null) + if [ -z "$out" ]; then + echo "READ $pkg -> EMPTY stdout (changesets would treat as UNPUBLISHED)" + else + echo "READ $pkg -> OK, latest=$(printf '%s' "$out" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{try{console.log(JSON.parse(d)["dist-tags"].latest)}catch{console.log("parse-fail")}})')" + fi + done + - name: Install Dependencies run: pnpm install --frozen-lockfile