@@ -209,14 +209,38 @@ jobs:
209209 # there and ignoring --target, which publishes an immutable release
210210 # against a commit nobody chose. Immutability makes the name
211211 # unusable afterwards even if the release is deleted.
212+ # Between the ref write and the release there is a tag with nothing
213+ # locking it, and the version job reads the highest tag, so a tag
214+ # left behind makes the next dispatch work out the version after
215+ # this one and skip this number silently.
216+ #
217+ # The cleanup is armed before the write rather than after it. Arming
218+ # it after leaves a window, short but real, where the tag exists and
219+ # nothing would take it back out. That ordering is only safe because
220+ # the cleanup checks that the tag is this run's before removing it,
221+ # so the 422 path below, where the tag belongs to someone else, does
222+ # not delete their tag. Every check that fails to confirm leaves the
223+ # tag alone, including an API error, since a tag left behind costs a
224+ # version number and a wrong delete costs a release.
225+ #
226+ # bash runs an EXIT trap when a signal terminates it, so this covers
227+ # a cancelled job as well as a failed command. SIGKILL defeats it,
228+ # and nothing here can change that.
229+ cleanup() {
230+ local ref_sha
231+ ref_sha=$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG}" --jq .object.sha 2>/dev/null) || return 0
232+ if [ "${ref_sha}" != "${COMMIT}" ]; then
233+ return 0
234+ fi
235+ if gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > /dev/null 2>&1; then
236+ return 0
237+ fi
238+ gh api -X DELETE "repos/${GITHUB_REPOSITORY}/git/refs/tags/${TAG}" > /dev/null 2>&1 || true
239+ }
240+ trap cleanup EXIT
212241 gh api "repos/${GITHUB_REPOSITORY}/git/refs" \
213242 -f "ref=refs/tags/${TAG}" \
214243 -f "sha=${COMMIT}" > /dev/null
215- # The tag now exists with no release locking it. Anything that stops
216- # this step from here has to take it back out: the version job reads
217- # the highest tag, so a tag left behind makes the next dispatch work
218- # out the version after this one and skip this number silently.
219- trap 'gh api -X DELETE "repos/${GITHUB_REPOSITORY}/git/refs/tags/${TAG}" > /dev/null 2>&1 || true' EXIT
220244 # --target is redundant now that the tag exists at the right commit,
221245 # and `gh` ignores it for an existing tag. It stays as the statement
222246 # of which commit this release is for.
0 commit comments