Skip to content

Commit c88ebfa

Browse files
widgetiiclaude
andauthored
ci(release): create the release once in a pre-job (kill tag race) (#175)
All three matrix targets (arm32/mips32/arm64) used to run actions/create-release@v1 against the same tag, so the two losers of the race logged `Validation Failed: already_exists / tag_name` on every run (non-fatal, but noisy and confusing). Hoist release creation into a single `release` pre-job that the `build` matrix `needs:`. It computes the release vars once and exposes them as job outputs (tag_name / git_hash / branch_name / head_tag), then ensures the release exists with a gh-CLI upsert (view-or-create) instead of the deprecated create-release action — so an existing rolling `latest` dev release is reused silently rather than erroring. Asset upload and the advisory Telegram notification stay per-target in the build matrix. Also add an explicit `permissions: contents: write` so release creation and asset upload don't depend on the repo's default token permissions. No behavioural change to the artifacts: assets are still uploaded with overwrite, and the `latest` tag still points where it did before. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d74090e commit c88ebfa

1 file changed

Lines changed: 71 additions & 33 deletions

File tree

.github/workflows/release.yml

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,75 @@ on:
88
- 'v*'
99
workflow_dispatch:
1010

11+
permissions:
12+
contents: write
13+
1114
jobs:
15+
# Create (or reuse) the GitHub release exactly once, before the build
16+
# matrix fans out. Previously every matrix target ran create-release
17+
# against the same tag, so all but the first failed with
18+
# "Validation Failed: already_exists / tag_name". Hoisting it into a
19+
# single pre-job removes that race; using a gh-CLI upsert (view-or-create)
20+
# also means an existing rolling `latest` dev release is reused silently
21+
# instead of erroring on every subsequent build.
22+
release:
23+
runs-on: ubuntu-latest
24+
outputs:
25+
tag_name: ${{ steps.vars.outputs.tag_name }}
26+
git_hash: ${{ steps.vars.outputs.git_hash }}
27+
branch_name: ${{ steps.vars.outputs.branch_name }}
28+
head_tag: ${{ steps.vars.outputs.head_tag }}
29+
steps:
30+
- uses: actions/checkout@v4
31+
with:
32+
fetch-depth: 0
33+
34+
- name: Compute release vars
35+
id: vars
36+
run: |
37+
HEAD_TAG=$(git tag --points-at HEAD)
38+
GIT_HASH=$(git rev-parse --short $GITHUB_SHA)
39+
BRANCH_NAME=$(echo $GITHUB_REF | cut -d'/' -f 3)
40+
if [ -z "$HEAD_TAG" ]; then
41+
TAG_NAME="latest"
42+
RELEASE_NAME="Development Build"
43+
PRERELEASE=true
44+
else
45+
TAG_NAME=${GITHUB_REF}
46+
RELEASE_NAME="Release ${GITHUB_REF}"
47+
PRERELEASE=false
48+
fi
49+
{
50+
echo "head_tag=$HEAD_TAG"
51+
echo "git_hash=$GIT_HASH"
52+
echo "branch_name=$BRANCH_NAME"
53+
echo "tag_name=$TAG_NAME"
54+
echo "release_name=$RELEASE_NAME"
55+
echo "prerelease=$PRERELEASE"
56+
} >> "$GITHUB_OUTPUT"
57+
58+
- name: Ensure release exists
59+
env:
60+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61+
TAG_NAME: ${{ steps.vars.outputs.tag_name }}
62+
RELEASE_NAME: ${{ steps.vars.outputs.release_name }}
63+
PRERELEASE: ${{ steps.vars.outputs.prerelease }}
64+
GIT_HASH: ${{ steps.vars.outputs.git_hash }}
65+
run: |
66+
if gh release view "$TAG_NAME" >/dev/null 2>&1; then
67+
echo "Release '$TAG_NAME' already exists — reusing it."
68+
else
69+
FLAGS=()
70+
[ "$PRERELEASE" = "true" ] && FLAGS+=(--prerelease)
71+
gh release create "$TAG_NAME" \
72+
--title "$RELEASE_NAME" \
73+
--notes "Automated build of $GIT_HASH" \
74+
--target "$GITHUB_SHA" \
75+
"${FLAGS[@]}"
76+
fi
77+
1278
build:
79+
needs: release
1380
runs-on: ubuntu-latest
1481

1582
strategy:
@@ -42,33 +109,16 @@ jobs:
42109

43110
env:
44111
UPX_VERSION: 4.2.3
112+
TAG_NAME: ${{ needs.release.outputs.tag_name }}
113+
GIT_HASH: ${{ needs.release.outputs.git_hash }}
114+
BRANCH_NAME: ${{ needs.release.outputs.branch_name }}
115+
HEAD_TAG: ${{ needs.release.outputs.head_tag }}
45116

46117
steps:
47118
- uses: actions/checkout@v4
48119
with:
49120
fetch-depth: 0
50121

51-
- name: Compute release vars
52-
run: |
53-
HEAD_TAG=$(git tag --points-at HEAD)
54-
GIT_HASH=$(git rev-parse --short $GITHUB_SHA)
55-
BRANCH_NAME=$(echo $GITHUB_REF | cut -d'/' -f 3)
56-
if [ -z "$HEAD_TAG" ]; then
57-
TAG_NAME="latest"
58-
RELEASE_NAME="Development Build"
59-
PRERELEASE=true
60-
else
61-
TAG_NAME=${GITHUB_REF}
62-
RELEASE_NAME="Release ${GITHUB_REF}"
63-
PRERELEASE=false
64-
fi
65-
echo "HEAD_TAG=$HEAD_TAG" >> $GITHUB_ENV
66-
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
67-
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
68-
echo "TAG_NAME=$TAG_NAME" >> $GITHUB_ENV
69-
echo "RELEASE_NAME=$RELEASE_NAME" >> $GITHUB_ENV
70-
echo "PRERELEASE=$PRERELEASE" >> $GITHUB_ENV
71-
72122
- name: Fetch toolchain
73123
run: |
74124
# Download to file (with retries) before extracting — piping
@@ -109,18 +159,6 @@ jobs:
109159
curl $TG_OPTIONS -H "Content-Type: multipart/form-data" -X POST https://api.telegram.org/bot$TG_TOKEN/sendMessage \
110160
-F chat_id=$TG_CHANNEL -F text="$TG_HEADER"
111161
112-
- name: Create release
113-
if: steps.build.outcome == 'success'
114-
uses: actions/create-release@v1
115-
continue-on-error: true
116-
env:
117-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
118-
with:
119-
tag_name: ${{ env.TAG_NAME }}
120-
release_name: ${{ env.RELEASE_NAME }}
121-
draft: false
122-
prerelease: ${{ env.PRERELEASE }}
123-
124162
- name: Upload ipctool to release
125163
if: steps.build.outcome == 'success'
126164
uses: svenstaro/upload-release-action@v2

0 commit comments

Comments
 (0)