From c9bf843f03649ba478cad2c1866b786fb3d81b43 Mon Sep 17 00:00:00 2001 From: Bart Waardenburg Date: Mon, 13 Jul 2026 14:47:40 +0200 Subject: [PATCH] ci: stop pushing the rolling v1 tag from release.yml GITHUB_TOKEN can never update a ref whose diff touches .github/workflows/ (the workflows permission is not grantable to it), so the in-workflow v1 push failed on the first release after any workflow edit (v3.4.2), and because it sat upstream of the publish jobs it turned a cosmetic step into a partial publish. The rolling v1/v3 tags are pushed by the maintainer release flow instead. --- .claude/rules/release-workflow.md | 2 +- .github/workflows/release.yml | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/.claude/rules/release-workflow.md b/.claude/rules/release-workflow.md index 46b0ab6a1..d17d28655 100644 --- a/.claude/rules/release-workflow.md +++ b/.claude/rules/release-workflow.md @@ -32,7 +32,7 @@ If a future PR wants to "simplify" by re-merging a `prep` and `publish` pair, re - **`cargo publish` uses `--no-verify`.** The verify-build runs `build.rs` of every transitive dependency with `CARGO_REGISTRY_TOKEN` in env; `--no-verify` skips that compile entirely. The tag's commit was already CI'd cleanly on `main`; the verify-build was a redundant safety net. - **Every publishable workspace crate must appear in `publish-crates`'s `for crate in ...` list, in dependency order.** The list is hardcoded; a publishable crate omitted from it breaks the chain at the first dependent that cannot resolve it on the index (incident v2.103.0: the new `fallow-output` / `fallow-engine` / `fallow-api` crates were missing, so only `fallow-types` published and the rest stalled at the prior version). The invariant `list == { workspace crates with publish != false }` is enforced by `crates/cli/tests/release_publish_list.rs`. Adding a publishable crate means inserting it after its dependencies; retiring one or setting `publish = false` means removing it. To recover a partially-failed release, re-run the publish from the RELEASE TAG (not `main`, which may carry later commits at the same not-yet-bumped version) with the same idempotent `cargo publish -p --no-verify` loop; it skips already-uploaded crates. - **`publish-crates` runs `setup-rust` with `cache-key: release-publish-crates`.** Dedicated cache scope so a poisoned workspace cache from a build job cannot influence `cargo package`. -- **`actions/checkout` carries `persist-credentials: false`** on every job EXCEPT where the job actually pushes git refs. The single exception is the `release` job's rolling `v1` tag push, which uses an explicit `https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}` URL to scope `GITHUB_TOKEN` to one step. +- **`actions/checkout` carries `persist-credentials: false`** on every job. No release.yml job pushes git tags anymore: the rolling `v1`/`v3` action tags are pushed by the maintainer's release flow (fallow-release skill step 8b), because `GITHUB_TOKEN` can never update a ref whose diff touches `.github/workflows/` (the `workflows` permission is not grantable to it), which made the old in-workflow `v1` push fail on the first release after any workflow edit (v3.4.2). The `docker-lockstep` job pushes a branch, but only via its explicitly-scoped token URL and never a tag. - **`expected_names[]` in `npm-publish` is load-bearing.** The 18-row TSV manifest produced by `npm-prep` is validated against (a) row count = 18, (b) per-row expected name in publish order, (c) per-row version equal to tag-derived `VERSION`, (d) the tarball's own `package/package.json` (`tar -xOf package/package.json | node -e ...`) matching the manifest row. Adding or removing a platform target requires updating BOTH the pack loops in `npm-prep` AND the `expected_names` array AND the `EXPECTED=18` constant. - **Job name `npm-publish` and workflow filename `release.yml` are part of npm trusted-publishing config.** Renaming either silently breaks OIDC. The `vscode-publish` job similarly relies on the workflow path for any future OIDC-style publishing. - **`shell: bash` on the `npm-publish` "Publish all tarballs" step is load-bearing.** The expected_names array lookup `${expected_names[$index]}` is 0-indexed in bash and 1-indexed in zsh; the explicit shell pin prevents a runner default change from silently breaking row matching. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8923c6544..75b0416e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -427,15 +427,11 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Update stable action tags - env: - # GH_TOKEN is interpolated only into the git push URL below, never - # into shell metacharacters, and the checkout step does not persist - # credentials, so this token's lifetime is scoped to this step. - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git tag -f v1 "${GITHUB_SHA}" - git push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}" refs/tags/v1 --force + # The rolling v1/v3 action tags are pushed by the maintainer's release + # flow, not from here: GITHUB_TOKEN can never update a ref whose diff + # touches .github/workflows/ (GitHub requires the workflows permission, + # which GITHUB_TOKEN cannot be granted), so the in-workflow push failed + # on the first release after any workflow edit (v3.4.2). # The Dockerfile pins ARG FALLOW_VERSION plus two per-arch sha256 digests by # hand. Nothing kept them in sync with releases: the pin sat on 2.94.0 for