From fbba9ffb029627e3699d1ccae0891010ddf31148 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 04:12:42 +0000 Subject: [PATCH] ci(release): resolve the next version without push credentials The release workflow's verify job failed at "Resolve next version" with EGITNOPERMISSION on its first manual dry run. semantic-release runs `git push --dry-run` against the repository URL before any plugin, dry run or not, and aborts if that fails. The verify job deliberately has no credentials (persist-credentials: false, contents: read), so the check could never pass against the GitHub URL; the script's own comment claimed "no token" and that was true of the plugins but not of this check. eng/next-release-version.mjs now hands semantic-release the local checkout as the repository URL. Pushing HEAD to the branch it already is needs no network and no token and changes nothing, and the commit analyser still sees the full history and tags that the checkout fetched. Reproduced against a fresh clone of main with the network's credentials removed: the unmodified script fails with "Authentication failed" and EGITNOPERMISSION, the modified one prints 1.3.0, the same version the unmodified script resolves with credentials. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01UWRpkQzKkNDYz3WiNgWvWU --- eng/next-release-version.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/eng/next-release-version.mjs b/eng/next-release-version.mjs index 5770d615..f508cd6f 100644 --- a/eng/next-release-version.mjs +++ b/eng/next-release-version.mjs @@ -5,8 +5,15 @@ // This is not a full `semantic-release --dry-run`. That loads the GitHub and git plugins, // which check push permission and want a write token. The version is decided only by the // commit analyser, so that is the only plugin invoked. No token, no pack, no push. +// +// One check is not a plugin's, though: before any plugin runs, semantic-release itself runs +// `git push --dry-run` against the repository URL, dry run or not, and aborts with +// EGITNOPERMISSION if that fails. The `verify` job deliberately has no credentials, so the URL +// given below is this checkout rather than GitHub: pushing HEAD to the branch it already is +// needs no network, no token, and changes nothing. import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import semanticRelease from 'semantic-release'; const config = JSON.parse(readFileSync(new URL('../.releaserc.json', import.meta.url), 'utf8')); @@ -22,6 +29,7 @@ const result = await semanticRelease( // GitHub Actions sets CI=true; without this the library demands a write token even though // nothing here publishes. The analyser only needs the git history. ci: false, + repositoryUrl: fileURLToPath(new URL('..', import.meta.url)), branches: config.branches, tagFormat: config.tagFormat, plugins: versionPlugins,