Skip to content

UID2-7340: publish service releases as pre-releases instead of drafts#242

Merged
BehnamMozafari merged 9 commits into
mainfrom
bmz-UID2-7340-publish-prereleases
Jun 23, 2026
Merged

UID2-7340: publish service releases as pre-releases instead of drafts#242
BehnamMozafari merged 9 commits into
mainfrom
bmz-UID2-7340-publish-prereleases

Conversation

@BehnamMozafari

@BehnamMozafari BehnamMozafari commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

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). Latest is never set automatically — it stays a deliberate manual promotion.

Changes

actions/shared_create_releases/action.yaml

  • New prerelease input (default 'false'): prerelease: 'true' publishes a pre-release; omitted/'false' keeps the original draft behaviour. draft is derived (draft when not pre-release); the action can never auto-publish a full Latest release.
  • Explicit tag_name: v<version> — softprops defaults it to github.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 from commit_pr_and_merge.
  • Resolve previous published tag (pre-release cuts only): computes the changelog fromTag from the most recent published release, so a tagged-but-unpublished run self-heals on the next cut. Explicit from_tag still wins; drafts/empty fall back to mikepenz auto-detection. Runs under set -euo pipefail so a gh failure fails the cut instead of silently building the changelog over the wrong window.
  • Verify release tag exists (pre-release cuts only): fails if 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 via commit_pr_and_merge before publishing; this guards future callers that don't.

Docker reusable workflows set prerelease: 'true' (all @v3 consumers inherit it, no per-repo change):

  • shared-publish-to-docker-versioned.yaml
  • shared-publish-java-to-docker-versioned.yaml

The Maven/PyPI/NuGet/iOS workflows are unchanged (omit prerelease → draft).

README.md — documents the prerelease input, the fromTag self-heal, and the tag-existence contract.

Notes

  • Delete Draft Releases step left as-is — scope-down tracked in UID2-7070.
  • Blast radius: the action ships under @v3 (moved only when this repo cuts a release). Validate a real service publish before relying on it.
  • uid2-operator publish-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):

Check Result
prerelease: 'true' draft:false, prerelease:true; GET /releases/tags/v<ver>200 (was 404 on drafts)
omitted draft created (original behaviour preserved)
tag_name release attaches to v<ver>, not the dispatch branch
fromTag self-heal changelog spans from the most recent published release, skipping a tagged-but-unpublished gap

🤖 Generated with Claude Code

…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>
BehnamMozafari and others added 7 commits June 23, 2026 13:20
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>
@BehnamMozafari BehnamMozafari changed the title UID2-7340: publish releases as pre-releases (services) / full releases (SDKs) instead of drafts UID2-7340: publish service releases as pre-releases instead of drafts Jun 23, 2026
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
run: |
set -uo pipefail
tag="v${{ inputs.new_version }}"
if gh api "repos/${{ github.repository }}/git/ref/tags/$tag" >/dev/null 2>&1; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. New transient failure point on every service release. Any non-404 failure of this gh api call — auth blip, 5xx, rate-limit, network — now aborts the publish even when the tag is actually present.
  2. 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.

@BehnamMozafari BehnamMozafari merged commit 03cb0bc into main Jun 23, 2026
3 checks passed
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.

2 participants