Skip to content

[release gate] make Release workflow tag-idempotent and clean orphan drafts #6

Description

@Vonng

Problem

The mc Release workflow can be triggered both by a RELEASE.* tag push and by workflow_dispatch. Without per-tag serialization and explicit retry semantics, concurrent runs created duplicate same-tag Draft releases and split assets across failed jobs.

Incident evidence:

  • Published RELEASE.2026-08-26T00-00-00Z plus orphan Draft release ID 377272417 with 7 duplicate assets.
  • Published RELEASE.2026-08-26T17-15-27Z plus orphan Draft release ID 377309241 with 7 duplicate assets.
  • Tag-push Release run 32995378797 failed after a same-tag manual run had already created assets.

Selected semantics

  • Serialize release jobs by resolved tag with cancel-in-progress: false.
  • Validate the RELEASE.YYYY-MM-DDTHH-MM-SSZ tag, require it to resolve to the checkout, and pass it explicitly as GORELEASER_CURRENT_TAG.
  • Fail fast if exactly one published release exists for the tag.
  • Replace exactly one existing Draft from scratch through GoReleaser's replace_existing_draft.
  • Fail fast when more than one matching release exists; duplicate Draft cleanup is deliberate and manual.
  • Keep per-asset replacement disabled. Retry replaces the whole Draft; package upload has no --clobber.
  • Reconfirm that exactly one Draft exists immediately before package upload.
  • Leave a failed run's Draft available for inspection; the next retry replaces it.
  • Remove orphan Drafts 377272417 and 377309241 without touching the corresponding published releases.

Local implementation

  • Commit: f08111c6 ci: make release retries tag-idempotent
  • Release-state decisions live in buildscripts/check-release-state.sh with fixture coverage in _test.sh.
  • test-release.yml watches, syntax-checks, executable-checks, and runs both scripts.
  • The commit is local and unpushed; the hash will resolve on GitHub only after the follow-up PR is published.

Verification and close criteria

  • actionlint v1.7.12 passes across all workflows.
  • Exact GoReleaser v2.17.1 validates .github/goreleaser.yml.
  • Fixture tests cover absent, required-Draft absent, Draft, required Draft, published, duplicate Drafts, wrong tag, malformed JSON, and invalid tag.
  • Live read-only checks reject the current published tag, accept an absent tag before build, reject absent/published state when a Draft is required, and fail closed on an inaccessible repository.
  • make verifiers, go test ./..., bash syntax, and git diff --check pass locally.
  • Push a focused PR and require hosted CI green.
  • Exercise one controlled Draft build only after all source blockers are closed.
  • Verify a retry leaves exactly one Draft with one copy of every archive and package asset.
  • Verify a published-release rerun fails before any build or upload.

Do not create a new production tag until the remaining remote and controlled-Draft checks are complete. The existing published releases remain unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions