Skip to content

Make release workflow idempotent on duplicate tag-push deliveries#95

Merged
devrimcavusoglu merged 1 commit into
mainfrom
fix/release-duplicate-trigger
May 13, 2026
Merged

Make release workflow idempotent on duplicate tag-push deliveries#95
devrimcavusoglu merged 1 commit into
mainfrom
fix/release-duplicate-trigger

Conversation

@devrimcavusoglu
Copy link
Copy Markdown
Owner

Summary

Observed during the v0.3.1 cut: a single git push origin v0.3.1 produced two parallel "Release" workflow runs (25805331860 and 25805336177) — webhook re-delivery race. Both goreleaser jobs raced on asset upload; the loser failed with 422 already_exists on every asset. The release itself was fine, but the failed run is visible noise on the Actions tab.

Fix

Two changes to .github/workflows/release.yml, both required:

  1. Concurrency group release-<ref> with cancel-in-progress: false — serializes simultaneous runs for the same tag. Do not cancel-in-progress because a real publish must not be interrupted mid-upload.
  2. Pre-flight gh release view check before goreleaser — if a release for the tag already exists, skip goreleaser and let the job exit green. Combined with the concurrency lock, the duplicate run reliably becomes a no-op.

Either change alone is insufficient:

  • Serialization without idempotency still hits 422 on the second run.
  • Idempotency without serialization races — the release is created ~25s into the first run, after the second run's pre-check would have executed.

Test plan

  • CI green on this PR (workflow file only — no functional change to skern itself)
  • After merge, next release tag push produces exactly one green Release run (any duplicate webhook becomes a no-op skip)

🤖 Generated with Claude Code

Observed on v0.3.1: a single `git push origin v0.3.1` produced two
parallel "Release" workflow runs (webhook re-delivery race). Both
goreleaser jobs raced on asset upload; the loser failed with
`422 already_exists` on every asset. The release itself was fine but
the failed run is visible noise.

Two changes, both required:

1. Concurrency group `release-<ref>` with `cancel-in-progress: false`
   so simultaneous runs for the same tag are serialized instead of
   racing. We do not cancel-in-progress because a real publish must
   not be interrupted mid-upload.

2. Pre-flight `gh release view` check before invoking goreleaser. If
   a release for the tag already exists (i.e. an earlier run for the
   same tag already published), skip goreleaser and let the job exit
   green. The concurrency lock guarantees the existence check happens
   AFTER the first run completes, so this is reliable.

Either change alone is insufficient: serialization without idempotency
still produces a `422` on the second run; idempotency without
serialization races because the release is created ~25s into the
first run, after the second run's pre-check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@devrimcavusoglu devrimcavusoglu merged commit ba703bf into main May 13, 2026
5 checks passed
@devrimcavusoglu devrimcavusoglu deleted the fix/release-duplicate-trigger branch May 13, 2026 18:53
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