Skip to content

ci(release): tag-last release pipeline — gate + build everything before the tag exists - #6890

Merged
proggeramlug merged 2 commits into
mainfrom
ci/release-tag-last
Jul 27, 2026
Merged

ci(release): tag-last release pipeline — gate + build everything before the tag exists#6890
proggeramlug merged 2 commits into
mainfrom
ci/release-tag-last

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

Flips the release pipeline to tag-last: instead of pushing a vX.Y.Z tag and hoping the pipeline goes green, you dispatch release-packages.yml with cut_release=true, it gates on tests + builds every leg, and only then creates the tag + GitHub Release and publishes. A red gate or a broken build leg now costs nothing — no burned tag, no half-published release.

How

New preflight job resolves the run mode + tag once, up front; every downstream job reads needs.preflight.outputs.{tag,version}:

mode trigger behavior
release release: published (legacy) unchanged tag-first path, still fully supported
cut-release dispatch, cut_release=true tag-LAST: gate → build → create tag+release → publish
republish dispatch, existing_tag=vX.Y.Z rebuild + re-run publish legs for an existing release
stage plain dispatch build-only smoke run, touches no release

Footgun removed: every publish leg used to fall back to gh release list --limit 1 when there was no release event — a pre-tag dispatch would upload new binaries onto the previous release and point homebrew/apt/winget at the wrong version. That fallback is gone everywhere; republish takes an explicit tag.

Key pieces:

  • await-tests (cut-release mode): dispatches test.yml / simctl-tests.yml on the pinned branch if no run exists on the SHA (fails fast if the ref moved), then polls by head SHA as before. Bypassed for republish/stage — the old manual lever.
  • create-release (cut-release only, requires build + build-cross fully green): re-checks the tag is still free, creates it at github.sha with notes from changelog.d/ (cut_release_notes.sh --notes-only), then dispatches the tag-rider workflows (docs, benchmark, container-tests) — a GITHUB_TOKEN-created tag emits no push event, and the same token-suppression means the release: published event can't re-trigger this workflow (no double publish). benchmark.yml's hard-fail release gate still engages: startsWith(github.ref, 'refs/tags/v') holds when dispatched with --ref <tag>; container-tests Layer D likewise.
  • publish-assets: single upload path for all modes (the build legs no longer touch the release at all). Host archives are a hard requirement; cross bundles stay best-effort on the legacy paths, matching the old per-leg semantics.
  • homebrew / winget / update-workers hang off publish-assets (they consume release-download URLs); apt hangs off tag-existence and runs in parallel; npm-publish stays last, idempotent, and now also waits for build-cross to complete (removes the old race where stage-npm.sh backfilled libperry_ui_android.a from whatever cross artifacts happened to exist).
  • The signed CLI update manifests get the tag from preflight — in cut-release mode the tag doesn't exist yet, but its name is already fixed by Cargo.toml.
  • permissions.actions: read → write for the gate/tag-rider dispatches.

scripts/cut_release_notes.sh grows two modes: --notes-only (print concatenated fragments; used by CI) and --fold vX.Y.Z (post-release fragment removal, scoped to the fragments recorded at the tag so entries merged after the release SHA survive for the next one). Legacy single-arg behavior unchanged.

/release skill rewritten for the new flow: pin release/vX.Y.Z branch → dispatch with cut_release=true → watch → --fold + drop the pin branch. The pinned branch matters because workflow_dispatch runs on ref tips and test.yml's test-<ref> concurrency group would cancel a running nightly if dispatched on main.

Drive-by: the homebrew tap commit message referenced a never-set shell $VERSION — every tap commit was titled just "perry ". Now uses the step output.

Failure semantics

  • Preflight red (tag exists / Cargo.tomlCLAUDE.md drift / no fragments): fails in seconds, nothing runs.
  • Gate/build red: nothing tagged, nothing published; fix and re-dispatch — the same version number can try again.
  • Publish leg red (after the tag exists): gh run rerun <id> --failed reruns the failed legs and everything downstream; npm skips already-published versions.

Testing

  • actionlint clean (same 7 pre-existing shellcheck style/info findings as main, line-shifted).
  • shellcheck + bash -n clean on cut_release_notes.sh.
  • Job-graph conditions walked through for all four modes (skipped-needs propagation via !cancelled() guards on publish-assets / apt / npm-publish; default needs-semantics elsewhere).
  • Real validation is the next release: plan is a stage dispatch first (build-only smoke), then the first cut_release=true release.

Summary by CodeRabbit

  • New Features
    • Releases now complete testing and artifact builds before creating tags and GitHub Releases.
    • Added explicit options for cutting releases, republishing existing tags, and publishing npm packages.
    • Added notes-only and post-release changelog cleanup modes.
  • Bug Fixes
    • Manual builds no longer fall back to the latest release automatically.
    • Releases are prevented when required tests fail or run against the wrong commit.
  • Documentation
    • Updated release guidance for the tag-last workflow, with legacy fallback instructions retained.

Ralph Küpper added 2 commits July 27, 2026 06:36
…re the tag exists

release-packages.yml gains a preflight job that resolves one of four modes
(release / cut-release / republish / stage) and the tag ONCE; every
downstream job takes tag/version from its outputs. The per-leg 'latest
release' fallback is gone — it used to upload pre-tag dispatches onto the
PREVIOUS release.

cut-release (workflow_dispatch, cut_release=true) is the new default flow:
await-tests dispatches test.yml/simctl-tests.yml on the candidate SHA when
needed and polls by head SHA; build + build-cross must go fully green; only
then does create-release make the vX.Y.Z tag + GitHub Release (notes from
changelog.d via cut_release_notes.sh --notes-only) and dispatch the
tag-rider workflows (docs, benchmark, container-tests) that a
GITHUB_TOKEN-created tag cannot fire itself. publish-assets is now the
single upload path for all modes; homebrew/winget/update-workers hang off
it, apt off tag-existence, npm still last and idempotent.

A red gate or build leg now costs nothing: no burned tag, no half-published
release. The legacy release-event path and an explicit existing_tag
republish lever keep working; plain dispatch becomes a harmless stage-only
smoke build.

cut_release_notes.sh grows --notes-only (CI) and --fold vX.Y.Z (tag-scoped
fragment removal after a tag-last release). /release skill rewritten for
the new flow. Also fixes the homebrew tap commit message, which always said
'perry ' because $VERSION was never set in that step.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 365e471f-5ef5-4aa9-8e4c-73441c2f53df

📥 Commits

Reviewing files that changed from the base of the PR and between fd74751 and 03d6db8.

📒 Files selected for processing (5)
  • .claude/skills/release/SKILL.md
  • .github/workflows/release-packages.yml
  • changelog.d/6890-tag-last-release-pipeline.md
  • changelog.d/README.md
  • scripts/cut_release_notes.sh

📝 Walkthrough

Walkthrough

The release process now validates tests and builds before CI creates the release tag and GitHub Release. Release assets are collected as workflow artifacts, published centrally, and downstream package jobs use resolved release metadata. Changelog fragments support separate notes generation and post-release folding.

Changes

Release pipeline

Layer / File(s) Summary
Tag-last operator flow
.claude/skills/release/SKILL.md
Release guidance now uses pinned release branches, CI dispatch, post-release fragment folding, legacy fallback handling, and tag-last failure rules.
Changelog fragment lifecycle
scripts/cut_release_notes.sh, changelog.d/README.md, changelog.d/6890-tag-last-release-pipeline.md
Release notes can be generated without side effects, while --fold removes fragments recorded at a release tag and commits the cleanup.
Preflight and build gating
.github/workflows/release-packages.yml
Workflow modes, tags, and versions are resolved centrally; tests are checked against the pinned SHA, and build signing uses the resolved tag without latest-release fallback.
Release creation and publishing
.github/workflows/release-packages.yml
Build artifacts are uploaded for centralized publication, then CI creates the tag and release before downstream Homebrew, APT, winget, worker, and npm jobs proceed.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReleaseBranch
  participant Preflight
  participant AwaitTests
  participant Build
  participant CreateRelease
  participant PublishAssets
  ReleaseBranch->>Preflight: dispatch cut-release workflow
  Preflight->>AwaitTests: resolve mode, tag, version, and SHA
  AwaitTests->>Build: pass after exact-SHA test checks
  Build->>CreateRelease: provide completed artifacts
  CreateRelease->>PublishAssets: create tag and GitHub Release
  PublishAssets->>ReleaseBranch: publish release assets
Loading

Possibly related PRs

  • PerryTS/perry#6751: Modifies the changelog fragment system and release-note folding behavior used by this pipeline.

Suggested reviewers: thehypnoo

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/release-tag-last

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug merged commit 5b4e70e into main Jul 27, 2026
2 of 3 checks passed
@proggeramlug
proggeramlug deleted the ci/release-tag-last branch July 27, 2026 04:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant