UID2-7340: publish service releases as pre-releases instead of drafts#242
Merged
Conversation
…f drafts Stop creating draft GitHub Releases (which require a manual publish click that is usually skipped, silently losing release notes and 404ing on get-by-tag). Publish each release directly: - pre-release for deployed services (docker workflows) - full Latest for registry packages (maven/pypi/nuget/ios) shared_create_releases: - add `prerelease` input (default 'false'); set draft:false unconditionally - resolve the previous *published* release tag for the changelog fromTag so a tagged-but-unpublished run self-heals on the next release; `from_tag` still wins as an explicit override - reword description Wire the 6 reusable publish workflows: docker x2 -> prerelease true; maven/pypi/nuget -> false; ios -> false with a new `prerelease` workflow_call input so genuine beta/rc cuts can still be marked pre-release. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
softprops defaults tag_name to github.ref_name, which is the dispatch branch (e.g. main) — not the version tag. Drafts masked this (a draft carries no tag until manually published, as seen on existing releases with tag_name=""), but a published release needs the tag now, or get-by-tag would still 404. Pass tag_name: v<version> explicitly; the tag already exists at this point (pushed by commit_pr_and_merge in every publish workflow). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gh applies --jq per page and concatenates results, so map(select(...)) | .[0] emits one line per page on repos with >30 releases (several @V3 consumers exceed this), making tag=$(...) multi-line and corrupting $GITHUB_OUTPUT and the changelog fromTag. Fetch a single page of 100 instead; the newest published release sits at the top of the list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removed unnecessary comments and retained essential information.
Per updated ticket scope, registry/SDK packages (Maven, PyPI, NuGet, iOS) stay on the legacy draft flow for now rather than full Latest releases. Only deployed services (docker) switch to published pre-releases. - add `draft` input (default 'true') to shared_create_releases; the action now drafts by default and services opt out with draft:'false' - docker / java-docker workflows set draft:'false' + prerelease:'true' - revert Maven/PyPI/NuGet/iOS workflows (no prerelease arg; iOS prerelease workflow_call input removed) — they fall back to the draft default - skip previous-published-tag resolution for drafts (legacy git-tag auto-detect) - README updated to document draft vs prerelease Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…re-releases Remove the prerelease input and derive it from draft: a published release (draft:false, services) is always a pre-release, and Latest is only ever set by manual promotion — the action can no longer auto-publish a full Latest release. Docker workflows now just set draft:'false'. Trim now-stale comments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ead of draft Cleaner caller model: services set prerelease:'true' to publish a pre-release; omitting it keeps the original draft behaviour (registry/SDK packages). draft is derived (draft when not prerelease); Latest is never set automatically. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a guard on the pre-release path that fails if the v<version> tag does not already exist, instead of letting softprops auto-create it at the checked-out commit and durably mis-tag the release. Every current consumer pushes the tag via commit_pr_and_merge before publishing; this protects future callers that don't. Harden Resolve previous published tag: set -euo pipefail so a gh failure (auth/5xx/rate-limit) aborts the step rather than silently falling back to mikepenz over the wrong changelog window — a genuine empty result (first cut) still falls back as intended. Note that .[0] is newest-by-creation, not highest semver. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jon8787
reviewed
Jun 23, 2026
| run: | | ||
| set -uo pipefail | ||
| tag="v${{ inputs.new_version }}" | ||
| if gh api "repos/${{ github.repository }}/git/ref/tags/$tag" >/dev/null 2>&1; then |
Contributor
There was a problem hiding this comment.
Verify release tag exists: a transient API error is indistinguishable from a missing tag
Failing closed here is the right instinct — better to abort than let softprops auto-create v<version> at the checked-out SHA and durably mis-tag the release. Two downsides worth weighing:
- New transient failure point on every service release. Any non-404 failure of this
gh apicall — auth blip, 5xx, rate-limit, network — now aborts the publish even when the tag is actually present. - Misleading error. When the real cause is a 5xx/rate-limit rather than a missing tag, the message still reads
does not exist, which will misdirect whoever debugs the failed release.
Consider distinguishing a genuine 404 from a transport/5xx error — e.g. branch on the HTTP status, or add a short retry on non-404 — so a real missing tag aborts loudly while a transient blip retries instead of masquerading as a missing tag.
jon8787
approved these changes
Jun 23, 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.
Summary
The shared release pipeline (
shared_create_releases) creates every GitHub Release as a draft. Publishing a draft is a manual click most people skip, so release notes are silently lost,GET /releases/tags/{tag}404s on the draft even though the git tag exists, and reading a draft needs a push-scoped token. This blocks UID2-6766 (release notes at the deploy approval gate) and the UID2-7327 read-only release PAT.This PR lets deployed services publish a pre-release instead of a draft — durable and fetchable by tag, without claiming GA. Registry/SDK packages (Maven, PyPI, NuGet, iOS) keep their existing draft flow (a follow-up can move them later).
Latestis never set automatically — it stays a deliberate manual promotion.Changes
actions/shared_create_releases/action.yamlprereleaseinput (default'false'):prerelease: 'true'publishes a pre-release; omitted/'false'keeps the original draft behaviour.draftis derived (draftwhen not pre-release); the action can never auto-publish a fullLatestrelease.tag_name: v<version>— softprops defaults it togithub.ref_name(the dispatch branch), which drafts masked since they carry no tag; a published release needs the tag or get-by-tag still 404s. The tag already exists fromcommit_pr_and_merge.fromTagfrom the most recent published release, so a tagged-but-unpublished run self-heals on the next cut. Explicitfrom_tagstill wins; drafts/empty fall back to mikepenz auto-detection. Runs underset -euo pipefailso aghfailure fails the cut instead of silently building the changelog over the wrong window.v<version>doesn't already exist, rather than letting softprops auto-create the tag at the checked-out commit. Every current consumer pushes the tag viacommit_pr_and_mergebefore publishing; this guards future callers that don't.Docker reusable workflows set
prerelease: 'true'(all@v3consumers inherit it, no per-repo change):shared-publish-to-docker-versioned.yamlshared-publish-java-to-docker-versioned.yamlThe Maven/PyPI/NuGet/iOS workflows are unchanged (omit
prerelease→ draft).README.md— documents theprereleaseinput, the fromTag self-heal, and the tag-existence contract.Notes
Delete Draft Releasesstep left as-is — scope-down tracked in UID2-7070.@v3(moved only when this repo cuts a release). Validate a real service publish before relying on it.uid2-operatorpublish-all-operators.yaml(private-op artifacts) is handled in a separate PR per the proposal.Validation
Smoke-tested by calling
shared_create_releases@<branch>directly in a throwaway repo (UnifiedID2/bmz-prerelease-smoke):prerelease: 'true'draft:false, prerelease:true;GET /releases/tags/v<ver>→ 200 (was 404 on drafts)tag_namev<ver>, not the dispatch branch🤖 Generated with Claude Code