Skip to content

Commit 2b7513b

Browse files
UID2-7340: verify release tag exists; harden previous-tag resolution
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>
1 parent 14dd20a commit 2b7513b

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ All shared publish workflows generate release notes via the `actions/shared_crea
1111
- The native format hardcodes `* TITLE by @AUTHOR in #NUMBER`. Authors are noise to the public consumers of these releases; mikepenz's `pr_template` lets us emit `- TITLE - ( PR: #NUMBER )` instead.
1212
- The composite embeds a per-platform install snippet (`docker pull`, `pip install`, `dotnet add package`, Maven `<dependency>`) above the changelog.
1313

14-
`shared_create_releases` supports `publish_platform` values `Docker`, `Maven`, `PyPI`, `NuGet`, `iOS`. It runs four steps internally: Resolve previous published tag (gh) → Build Changelog (mikepenz) → Delete Draft Releases → Create Release (softprops). The `prerelease` input (default `'false'`) controls the release type:
14+
`shared_create_releases` supports `publish_platform` values `Docker`, `Maven`, `PyPI`, `NuGet`, `iOS`. Internally it builds the changelog (mikepenz), deletes stale drafts, and creates the release (softprops); on the pre-release path it additionally resolves the previous published tag (gh) and verifies the `v<version>` tag exists before publishing. The `prerelease` input (default `'false'`) controls the release type:
1515

1616
- omitted / `prerelease: 'false'` (default) — creates a **draft** release (the original behaviour, still requires a manual "Publish" click). The Maven/PyPI/NuGet/iOS (registry/SDK) workflows keep this default for now.
17-
- `prerelease: 'true'` — publishes a **pre-release** immediately (durable + fetchable by tag, without claiming GA). The shared docker workflows set this for deployed-service builds. `Latest` is never set automatically — it stays a deliberate manual promotion.
17+
- `prerelease: 'true'` — publishes a **pre-release** immediately (durable + fetchable by tag, without claiming GA). The shared docker workflows set this for deployed-service builds. `Latest` is never set automatically — it stays a deliberate manual promotion. The `v<version>` tag must already exist (pushed earlier by `commit_pr_and_merge`); the action verifies this and fails rather than letting softprops auto-create the tag at the wrong commit.
1818

1919
When `is_release` is `false` (Snapshot/pre-release build) the action is a no-op, so callers can invoke it unconditionally.
2020

actions/shared_create_releases/action.yaml

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,19 @@ runs:
203203
env:
204204
GH_TOKEN: ${{ inputs.github_token }}
205205
run: |
206+
set -euo pipefail
206207
# No --paginate: gh applies --jq per page and concatenates, so map(...)
207-
# would emit one line per page and corrupt the output. One page of 100 is
208-
# plenty to find the newest published (draft==false) release at the top.
208+
# would emit one line per page and corrupt the output. GitHub returns
209+
# releases sorted by created_at descending, so the newest published
210+
# (draft==false) release is always on page 1 — .[0] picks it without
211+
# paging. Note .[0] is newest-by-CREATION, not highest semver: a
212+
# backfilled or re-created release shifts it; use from_tag to override.
213+
#
214+
# set -euo pipefail so a gh failure (auth/5xx/rate-limit) aborts the
215+
# step rather than silently yielding "" and falling back to mikepenz
216+
# auto-detection over the wrong window. A genuine empty result (no prior
217+
# published release, e.g. the first cut) still yields "" with a zero
218+
# exit and falls back as intended — the failure and empty cases differ.
209219
tag=$(gh api "repos/${{ github.repository }}/releases?per_page=100" \
210220
--jq 'map(select(.draft==false)) | .[0].tag_name // ""')
211221
echo "tag=$tag" >> "$GITHUB_OUTPUT"
@@ -221,6 +231,30 @@ runs:
221231
env:
222232
GITHUB_TOKEN: ${{ inputs.github_token }}
223233

234+
# A published (pre-)release must attach to a tag that already exists.
235+
# softprops/action-gh-release otherwise CREATES the tag at the checked-out
236+
# SHA, durably mis-tagging the release at the wrong commit. The v<version>
237+
# tag is pushed earlier by commit_pr_and_merge (via
238+
# shared-increase-version-number, or the java docker workflow), so every
239+
# current consumer satisfies this. The guard turns a silent mis-tag into a
240+
# loud failure if a caller ever wires the publish without that preceding
241+
# tag step. Drafts don't materialise tags, so this only applies to the
242+
# pre-release (published) path. See UID2-7340.
243+
- name: Verify release tag exists
244+
if: ${{ inputs.is_release == 'true' && inputs.prerelease == 'true' }}
245+
shell: bash
246+
env:
247+
GH_TOKEN: ${{ inputs.github_token }}
248+
run: |
249+
set -uo pipefail
250+
tag="v${{ inputs.new_version }}"
251+
if gh api "repos/${{ github.repository }}/git/ref/tags/$tag" >/dev/null 2>&1; then
252+
echo "Verified release tag $tag exists."
253+
else
254+
echo "::error::Release tag $tag does not exist (or could not be read). A pre-release must attach to a tag pushed before this step by commit_pr_and_merge. Refusing to publish and auto-create the tag at the wrong commit."
255+
exit 1
256+
fi
257+
224258
- name: Delete Draft Releases
225259
if: ${{ inputs.is_release == 'true' }}
226260
uses: IABTechLab/uid2-shared-actions/actions/delete_draft_releases@v3

0 commit comments

Comments
 (0)