Skip to content

fix: create tag and release atomically via gh release create - #14

Merged
fiunchinho merged 1 commit into
mainfrom
atomic-tag-and-release-v2
Jun 3, 2026
Merged

fix: create tag and release atomically via gh release create#14
fiunchinho merged 1 commit into
mainfrom
atomic-tag-and-release-v2

Conversation

@fiunchinho

Copy link
Copy Markdown
Member

Stacked on #13. Base is consolidate-git-cliff-calls. The diff here shows only the atomicity fix. GitHub auto-retargets the base to main when #13 merges.

Summary

Today's flow is:

  1. git push origin TAG ← irreversible
  2. gh release create TAG ... ← can fail independently

If step 2 fails (GitHub API blip, transient permissions issue, network), we're left with a tag on the remote but no corresponding Release — a "stuck" state requiring manual cleanup, and architect's upload-release-assets job retries uselessly against a Release that doesn't exist.

GitHub's release-create endpoint accepts a tag_name + target_commitish and creates the tag as part of the same API call when it doesn't already exist. One operation, one failure point. Either both materialize or neither does.

Diff (~15 lines saved)

- - name: Push tag
-   if: steps.decide.outputs.tag != ''
-   env:
-     TAG: ${{ steps.decide.outputs.tag }}
-     GIT_ACTOR: ${{ github.actor }}
-   run: |
-     git config user.name  "$GIT_ACTOR"
-     git config user.email "${GIT_ACTOR}@users.noreply.github.com"
-     git tag -a "$TAG" -m "Release $TAG"
-     git push origin "$TAG"
-
- - name: Create GitHub Release
+ - name: Create release (and the tag, atomically)
    if: steps.decide.outputs.tag != ''
    env:
      ...
    run: |
      gh release create "$TAG" \
        --title "$TAG" \
        --notes-file release-notes.md \
-       --target "$GITHUB_SHA" \
-       --verify-tag
+       --target "$GITHUB_SHA"
  • Drops the separate "Push tag" step
  • Drops --verify-tag (was guarding against the failed-push case that no longer exists)
  • Drops git config user.name/email (was only needed for the annotated tag's metadata)

Bonus: closes a race with architect

Previously there was a ~1s window between tag push and release creation. CircleCI dispatches on the tag push, so architect/upload-release-assets could start polling for a release that didn't yet exist. Architect's built-in ~60s retry loop covered this race, but better to not race at all — with the atomic approach, the release exists the moment the tag does.

Trade-offs (explicit in the YAML comment)

Before After
Tag type Annotated (git tag -a with message) Lightweight (plain ref)
Tag attribution github.actor (PR merger) github-actions[bot]

For the giantswarm stack — git describe, architect/* tag filters, gitsemver, gh release view — both tag types behave identically. The tagger-attribution change is small: the release itself is created by github-actions[bot] in both cases; only the tag's annotation differed. Nobody routinely inspects tag annotations.

Test plan

  • After perf: consolidate git-cliff invocations behind a single install #13 merges (this PR's base auto-retargets to main).
  • After this PR squash-merges: v1.X.Y release exists with tag + notes, no separate "push tag" step in the workflow run output.
  • Backport scenario still works: pushing the workflow to a release-2.x branch and merging a conventional commit produces a Release on that branch's history (already exercised via --target $GITHUB_SHA).

@fiunchinho fiunchinho self-assigned this Jun 3, 2026
Base automatically changed from consolidate-git-cliff-calls to main June 3, 2026 13:44
Previously the workflow did `git push origin TAG` (irreversible) and
THEN `gh release create` as a separate step. If the second step failed
(GitHub API hiccup, network blip, transient permissions issue), we'd
be left with a tag on the remote but no corresponding Release —
broken state requiring manual cleanup, and architect's
upload-release-assets would retry uselessly against a Release that
doesn't exist.

GitHub's release-create endpoint accepts a `tag_name` + `target_commitish`
and creates the tag as part of the same API call when it doesn't already
exist. One operation, one failure point. Either both materialize or
neither does.

Side benefit: closes a small race window with architect's upload step.
Previously there was ~1s between tag push and release creation; if
CircleCI dispatched fast enough, upload-release-assets would start
polling for a release that didn't exist yet (architect's built-in
~60s retry loop covered this, but better to not race at all).

Trade-off named in the YAML comment: the tag becomes lightweight (a
plain ref) instead of annotated. For our use — git describe,
architect's tag filter, gitsemver, gh release view — both behave
identically. The `github.actor` attribution that the previous
annotated tag carried is also dropped; the release itself still
shows up as created by github-actions[bot] either way, so the
practical attribution doesn't really change.

Drop the now-redundant `--verify-tag` flag (it was guarding against
the failed-tag-push case that this change eliminates).
@fiunchinho
fiunchinho force-pushed the atomic-tag-and-release-v2 branch from 27900a3 to 30b63eb Compare June 3, 2026 13:45
@fiunchinho
fiunchinho merged commit 6213e39 into main Jun 3, 2026
1 check passed
@fiunchinho
fiunchinho deleted the atomic-tag-and-release-v2 branch June 3, 2026 13:46
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.

1 participant