From f7da982f8e482cdb93239d75913f0da9e6e60dc8 Mon Sep 17 00:00:00 2001 From: BryanFRD Date: Thu, 14 May 2026 14:55:47 +0200 Subject: [PATCH] fix(release): wait via list+filter and self-heal missing draft --- .github/workflows/publish.yml | 45 +++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 265f4bd..5c93fd4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -116,22 +116,47 @@ jobs: subject-path: artifacts/* - name: Wait for draft release to be visible # Defense against the race where this workflow (push:tags) fires - # before `ferrflow release --draft` (running in the CI workflow on - # the same commit) has finished the create-release API call. We - # poll up to ~2.5 min — the API call normally completes within a - # few seconds, this only saves us from eventual-consistency lag. + # before `ferrflow release --draft` (running in the CI workflow + # on the same commit) has finished the create-release API call. + # We poll up to ~5 min, then fall back to creating the draft + # release ourselves as a self-healing measure. + # + # Why list+filter instead of `gh release view "$TAG"` or `gh api + # /releases/tags/$TAG`: + # - The REST endpoint `/releases/tags/{tag}` excludes drafts + # entirely (returns 404). Verified locally on v4.7.2. + # - `gh release view "$TAG"` finds drafts but in v4.7.2's failed + # Publish run it returned 404 in the runner anyway, possibly + # due to gh-CLI version skew or token scope. We can't pin + # the runner gh version reliably. + # - `GET /releases?per_page=100` returns drafts when authed and + # is the most stable surface for "does this draft exist?". + # + # Self-heal: if the wait window expires, we attempt to create + # the release ourselves before failing. Covers the case where + # ferrflow's create_release call in CI silently warned-and- + # continued (see monorepo.rs:1339, follow-up filed at #439-fix). run: | TAG="${{ github.ref_name }}" - for i in $(seq 1 30); do - if gh release view "$TAG" >/dev/null 2>&1; then - echo "Release $TAG visible after $((i*5))s" + REPO="${{ github.repository }}" + for i in $(seq 1 60); do + ID=$(gh api "repos/$REPO/releases?per_page=100" \ + --jq ".[] | select(.tag_name == \"$TAG\") | .id" 2>/dev/null \ + | head -n1) + if [ -n "$ID" ]; then + echo "Release $TAG visible after $((i*5))s (id=$ID)" exit 0 fi - echo "Release $TAG not yet visible, retrying in 5s ($i/30)..." + echo "Release $TAG not yet visible, retrying in 5s ($i/60)..." sleep 5 done - echo "::error::Release $TAG never appeared after 150s — see ferrflow release log" - exit 1 + echo "::warning::Release $TAG never appeared after 300s — creating it now as self-heal" + gh release create "$TAG" \ + --draft \ + --title "$TAG" \ + --notes "Release notes will be filled by the post-publish step." \ + --target "${{ github.sha }}" + echo "Created draft release $TAG via self-heal" env: GH_TOKEN: ${{ secrets.FERRFLOW_TOKEN }} - name: Upload assets to draft release