fix(release): use the run-scoped token for the publish checkout (#15728)

* fix(release): stop authenticating the publish checkout with the bot PAT (#15726)

The publish job's read-only checkout used secrets.CI_BOT_PAT while every
other checkout in the release workflow uses the run-scoped token. Two
release runs failed at exactly that step with "could not read Username for
'https://github.com': terminal prompts disabled" (git exit 128), and both
passed on re-run. The bot PAT is already exported to the push and
release-creation steps that write, so the read-only fetch does not need it.

* test(ci): correct the release workspace wipe rationale (#15728)

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-28 02:19:10 +00:00
1 parent 127c2ab904
commit e305a774be
2 files changed
+25 -7

No files matched your search

+1 -1
View File
@@ -656,7 +656,7 @@ jobs:
- name: 'Checkout'
uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3
with:
token: '${{ secrets.CI_BOT_PAT }}'
# Read-only fetch; the bot PAT is only exported to the write steps.
ref: '${{ needs.prepare.outputs.release_sha }}'
fetch-depth: 0
persist-credentials: false
+24 -6
View File
@@ -295,12 +295,12 @@ describe('CUA release workflow', () => {
// The canonical workspace restoration script shared by all nine
// 'Restore workspace ownership' copies in release.yml. The full wipe (vs
// serve-ab.yml's keep-and-scrub) is deliberate on this lane: release
// checkouts carry CI_BOT_PAT and the npm OIDC id-token, so no pre-existing
// repo state may survive into them. Pin the WHOLE body by equality, the way
// review-worktree-cleanup-workflow.test.js pins its sweep copies: a
// commented-out find, an inserted early exit, or a uniformly dropped
// ownership ladder all ship green under substring pins, and each of those
// mutants reopens the incident class this step exists for.
// checkouts persist credentials and the publish job can mint the npm OIDC
// id-token, so no pre-existing repo state may survive into them. Pin the
// WHOLE body by equality, the way review-worktree-cleanup-workflow.test.js
// pins its sweep copies: a commented-out find, an inserted early exit, or a
// uniformly dropped ownership ladder all ship green under substring pins,
// and each of those mutants reopens the incident class this step exists for.
const canonicalWipe = `set -uo pipefail
# Reject symlinked or non-canonical paths before recursive changes.
RWS="\${RUNNER_WORKSPACE:?}"
@@ -3476,6 +3476,24 @@ exit 0`,
);
});
it('reads the release tree with the run-scoped token, not the bot PAT', () => {
// The publish checkout is read-only and the repository is public, so
// authenticating it with the long-lived bot PAT adds an auth dependency
// without adding access: a transient PAT rejection failed the release at
// Checkout (#15726). The PAT stays exported to the steps that write.
const steps = releaseYaml.jobs.publish.steps;
const checkout = steps.find((step) => step.name === 'Checkout');
expect(checkout.with.token).toBeUndefined();
expect(checkout.with['persist-credentials']).toBe(false);
expect(
steps.find((step) => step.id === 'push_release_branch').env.CI_BOT_PAT,
).toBe('${{ secrets.CI_BOT_PAT }}');
expect(
steps.find((step) => step.name === 'Create GitHub Release and Tag').env
.GITHUB_TOKEN,
).toBe('${{ secrets.CI_BOT_PAT }}');
});
it('serializes publish jobs per release tag', () => {
// The pre-push re-validation below is only sound while at most one
// publish job pushes and publishes a given version at a time; --force