ci: consolidate release into one dispatch-driven workflow#83
Open
yaseenisolated wants to merge 6 commits into
Open
ci: consolidate release into one dispatch-driven workflow#83yaseenisolated wants to merge 6 commits into
yaseenisolated wants to merge 6 commits into
Conversation
Replace the three event-chained release workflows (tag-and-release ->
deploy-release -> pypi-publish) with a single `Release` orchestrator that
runs the whole pipeline in one workflow_dispatch run, ordered by `needs:`:
validate -> test -> tag -> goreleaser -> verify-assets
-> wrapper-smoke -> pypi
-> cleanup (on failure)
Why: the old flow chained workflows via tag-push / release-published
events. A tag pushed with GITHUB_TOKEN does not trigger downstream
workflows, so the build silently never ran and the PyPI wrapper shipped
pointing at binaries that did not exist -> `pip install` then 404'd for
every user. Event chaining also hid where a release stalled.
This design removes the chaining entirely:
- One run, visible end to end. No silent hand-offs.
- Tag is created as a pipeline step (no PAT-trigger hack needed).
- Every job operates on the resolved release commit/tag, so GoReleaser's
git-state check can't be tripped by a dispatch running off a branch.
- verify-assets + wrapper-smoke gate PyPI on the binaries actually
existing and the wrapper actually running against the release; PyPI
(which can't be un-published) is the last step.
- cleanup deletes the tag + release on failure for clean re-runs.
Reusable sub-workflows (workflow_call): test.yml (shared with PR CI),
release-goreleaser.yml, release-pypi.yml. release-pypi.yml also fixes the
old "Test installation" step whose unconditional `exit 0` swallowed the
binary-download failure.
Docs (RELEASING.md, PYPI_RELEASE_STRATEGY.md) and the version-bump
comments updated to the dispatch flow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reuse the existing pypi-publish.yml filename (swap its trigger to workflow_call + apply the fixes) rather than renaming to release-pypi.yml. PyPI trusted publishing binds to the workflow filename, so keeping the name avoids reconfiguring the trusted publisher on PyPI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avoid the needless rename to release-goreleaser.yml — reuse the existing deploy-release.yml filename for the (now workflow_call-only) GoReleaser build. The only genuinely new file is the orchestrator (release.yml); no existing workflow is renamed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the Release workflow's `version` input optional. When left blank, validate resolves the latest stable vX.Y.Z tag and bumps the patch (e.g. v2.5.2 -> v2.5.3). Minor/major bumps still require an explicit version. Errors if no version is given and no stable tag exists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GitHub release is now created as a prerelease (.goreleaser.yaml prerelease: true). A new `promote` job flips a stable release to "Latest" only after verify-assets + wrapper-smoke + pypi all pass. Actual prerelease tags (-rc/-beta) are never promoted. - Add a `dry-run` input to the Release workflow: still pushes the tag and creates a real GitHub prerelease with binaries, and runs the verification jobs, but never promotes, never publishes to PyPI, and skips the Homebrew tap push + macOS notarization. For testing the pipeline end to end without affecting stable users. - Homebrew skip_upload is now keyed explicitly off the tag's semver prerelease component (not release.prerelease, which is forced true), so stable releases still update the tap. - cleanup now only tears down when the build itself fails; once goreleaser succeeds the prerelease is valid and is left in place on a later failure (avoids dangling a pushed Homebrew cask). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| workflow_call: | ||
| inputs: | ||
| tag: | ||
| description: "Release tag (e.g. v2.5.2 or 2.5.2)." |
Contributor
There was a problem hiding this comment.
the version should always have the v prefix
Suggested change
| description: "Release tag (e.g. v2.5.2 or 2.5.2)." | |
| description: "Release tag (e.g. v2.5.2)." |
Contributor
There was a problem hiding this comment.
nvm, I see it sorts it out below
jonoirwinrsa
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
v2.5.2 shipped to PyPI with no binaries, breaking
pip install cerebrium→cerebrium <cmd>with a 404 for every user. Root cause was the event-chained release design:tag-and-release.yml→ (tag-push event) →deploy-release.yml→ (release-published event) →pypi-publish.yml.GITHUB_TOKENdoes not trigger downstream workflows, so GoReleaser silently never ran — but the PyPI wrapper (a thin launcher that downloads those binaries at runtime) shipped anyway.What
Add a single
Releaseorchestrator (workflow_dispatchonly) that runs the whole pipeline in one visible run, ordered byneeds:— nothing triggers anything else via events:GITHUB_TOKEN-tag bug can't exist.verify-assets(all binaries + checksums present) andwrapper-smoke(the wrapper actually runs against the real release) both pass before the PyPI publish, which can't be un-published.cleanupdeletes the tag + release on any failure for clean re-runs.exit 0swallowed the binary-download failure (it logged the 404 and went green).Files (minimal churn — only one new file, no renames of existing workflows)
release.yml(new) — the orchestrator. Net-new; there was no orchestrator before.deploy-release.yml— same filename, reduced to the reusable (workflow_call) GoReleaser build; the test/smoke jobs moved into the orchestrator.pypi-publish.yml— same filename (keeps the PyPI trusted-publisher binding valid); trigger swapped fromrelease: publishedtoworkflow_call, plus the test-step fix and a binaries-exist guard.test.yml— now alsoworkflow_call-able, so the orchestrator runs the same matrix that gates PRs.tag-and-release.yml(its tag creation is now the orchestrator'stagjob).RELEASING.md,PYPI_RELEASE_STRATEGY.md) + version-bump comments updated to the dispatch flow.actionlint clean across all workflows.
pypi-publish.yml), so the existing entry should keep working — just confirm it points atpypi-publish.yml.Deploy Release/Tagwere required status checks, those names no longer exist as standalone triggerable workflows.gh workflow run release.yml --ref ci/consolidate-release -f version=v0.0.1-rc.1 # tear down: gh release delete v0.0.1-rc.1 --cleanup-tagHow to release after this merges
🤖 Generated with Claude Code