ci: create a GitHub Release per version tag - #40
Merged
Conversation
Mirrors continuo's release.yml, stripped to what actually applies here: no Helm chart, no Kubernetes deploy dir, no self-hosted runner or kind cluster in this repo, so none of retag-images/install-test/publish-chart/ smoke-published-chart exist. publish-pypi.yml and images.yml already do the real publishing work independently, each on their own push:tags trigger — this workflow's only job is to create the GitHub Release once both have succeeded for the tagged commit, since a tag also ships two ghcr.io images that PyPI's own release list knows nothing about. Triggered by the same tag push as its two siblings rather than by workflow_run watching them complete, since `needs:` cannot cross workflow files and this avoids relying on workflow_run's head_branch semantics for a tag-triggered source run. Instead it polls the Actions API for the sibling runs at this commit (event=push, to not pick up one of images.yml's pull_request runs) until both report a conclusion, exits non-zero on any non-success conclusion, and times out after 30 minutes per sibling. -test tags are excluded (TestPyPI dry run, no public Release). Release creation itself is idempotent (create-or-edit), matching continuo's approach; no CHANGELOG.md exists in this repo so notes are always --generate-notes. Verified: YAML validates (python3 -c "import yaml; yaml.safe_load(...)"). NOT verified: the actual cross-workflow wait/poll behavior, which needs a real tag push to exercise — there is no way to integration-test this locally or in a PR. Signed-off-by: Simone Carolini <simonecarolini.sc@gmail.com>
-f/-F on `gh api` switches the request to POST unless overridden — this listing endpoint only accepts GET, so the poll would 404 on its very first call under `set -e` and abort the job every time, regardless of whether publish-pypi.yml/images.yml actually succeeded. Verified against this repo's real API: the -f form 404s, the URL-query-string form returns real run data (tested against main's actual security.yml/ci.yml runs, both "success"). Also raised the job's timeout-minutes from 40 to 70: the two sequential wait_for calls can themselves take up to 30 minutes each — 60 minutes worst case on the polling math alone, before the siblings' own run time — so 40 could never actually cover it. Signed-off-by: Simone Carolini <simonecarolini.sc@gmail.com>
Backfilled from actual tag history (0.1.0 through 0.3.0, plus what's pending for 0.3.1), Keep a Changelog style. release.yml now extracts the section matching the pushed tag's version and uses it as the Release body, falling back to GitHub's generated notes if a tag has no matching section, instead of always using generated notes. Verified the extraction awk script against the real file for 0.3.1/0.3.0/0.2.1 (all extract correctly) and a nonexistent version (correctly falls through to the no-match branch). CONTRIBUTING.md now says where release notes come from, so the file doesn't silently go stale. Signed-off-by: Simone Carolini <simonecarolini.sc@gmail.com>
ci failed: CHANGELOG.md's 0.3.0 entry names continuo-validation-contract, the package that release actually replaced — the same reason docs/superpowers/'s dated design records are already exempt from this sweep (test_no_legacy_names.py's own words: rewriting them "would falsify the design history"). Extended the existing EXEMPT_PREFIXES mechanism rather than reword the changelog entry to dance around the name of what it's describing. Signed-off-by: Simone Carolini <simonecarolini.sc@gmail.com>
This was referenced Aug 21, 2026
Closed
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.
What this changes
Adds
.github/workflows/release.yml, adapted from continuo's workflow of the same name, so a version tag produces an actual GitHub Release.Why
publish-pypi.ymlandimages.ymlalready do the real work of a release (PyPI trusted publishing; building + smoke-testing + pushing both engine images to ghcr.io), each independently on their ownpush: tags: v*trigger. Neither of them creates a GitHub Release, and PyPI's release list only ever knows about the two packages — not the two ghcr.io images a tag also ships. A Release is the one place to say "this tag = these packages + these images."What was stripped from continuo's version, and why
continuo's release.yml is a 5-job pipeline (retag-images, install-test, publish-chart, smoke-published-chart, github-release) built around things this repo does not have: a Helm chart, a Kubernetes deploy directory, a self-hosted runner, a
kindcluster, and a separate deploy.yml that builds images once for retagging later. None of that applies here —images.ymlbuilds and pushes the images directly on the tag, it isn't retagging a pre-existing sha-tagged image. So this PR keeps only the equivalent of continuo'sgithub-releasejob.The one real design problem this repo's version has to solve that continuo's didn't
continuo's
github-releasejob usesneeds:on jobs in the same workflow file. Here, the two things this job needs to wait for —publish-pypi.ymlandimages.ymlsucceeding — are separate workflow files triggered independently by the same tag push, andneeds:can't cross files. I consideredworkflow_run(fire when either sibling completes, check if the other already finished too) but rejected it: it depends onworkflow_run.head_branchcorrectly carrying the tag name for a tag-triggered source run, which nothing else in this repo relies on, so I'd be introducing an assumption I can't verify.Instead,
release.ymltriggers on the samepush: tags: v[0-9]*as its siblings and polls the Actions API (gh api repos/.../actions/workflows/{file}/runs -f head_sha=... -f event=push) for each sibling's conclusion at this exact commit, up to a 30-minute timeout per sibling.event=pushspecifically avoids picking up one ofimages.yml'spull_request-triggered runs by accident. Any non-success conclusion (or a timeout) fails the job outright rather than silently skipping the Release.Other details:
-testtags (e.g.v0.3.1-test1) are excluded — those arepublish-pypi.yml's TestPyPI dry run, andimages.yml'spublishjob already skips itself for them by design.gh release view→editorcreate), matching continuo's pattern, in case this ever re-runs against an existing tag.CHANGELOG.mdexists anywhere in this repo (unlike continuo'sdeploy/continuo/CHANGELOG.md), so there's no changelog-extraction step to port — notes are always--generate-notes.How it was verified
python3 -c "import yaml; yaml.safe_load(open('.github/workflows/release.yml'))".v*tag push to exercise — a PR can't trigger it, and there's no local way to simulatepublish-pypi.yml/images.ymlcompleting. The next tag push (v0.3.1) will be the first real test; worth watching that run closely.Checklist
git commit -s)scripts/security-scan.shnot relevant (no dependency or credential changes)