-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ci): stop the nightly release job failing on an immutable tag #661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -687,40 +687,52 @@ jobs: | |
| run: | | ||
| DATE=$(date +'%Y-%m-%d') | ||
| TAG="nightly-${DATE}" | ||
| SHA=$(git rev-parse HEAD) | ||
|
|
||
| # This repository has GitHub immutable releases enabled, so the tag of a | ||
| # published release is permanently protected: pushing a delete is refused | ||
| # with "GH013 ... Cannot delete this tag". The previous | ||
| # delete-then-recreate flow swallowed that refusal as a warning and then | ||
| # died on "tag already exists", failing the whole release job on any run | ||
| # that happened after a release already existed for the same date -- a | ||
| # manual re-dispatch on a day the scheduled nightly had already | ||
| # published, for instance -- even with every build job green. | ||
| # Tagging is idempotent now and never fails on an already-published tag. | ||
| REFS=$(git ls-remote --tags origin "refs/tags/$TAG" "refs/tags/$TAG^{}" || true) | ||
|
|
||
| if [ -z "$REFS" ]; then | ||
| echo "No existing remote tag $TAG; creating it at $SHA." | ||
| git tag -f -a "$TAG" -m "Nightly build $DATE" | ||
| git push origin "refs/tags/$TAG" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Delete existing local tag if it exists | ||
| echo "Checking for existing local tag: $TAG" | ||
| if git rev-parse "$TAG" >/dev/null 2>&1; then | ||
| echo "Deleting existing local tag: $TAG" | ||
| git tag -d "$TAG" | ||
| else | ||
| echo "No existing local tag found" | ||
| # An annotated tag reports the tag object on the plain line and the | ||
| # commit it points at on the "^{}" line; prefer the latter. | ||
| TAGGED=$(printf '%s\n' "$REFS" | awk '$2 ~ /\^\{\}$/ { print $1 }' | head -n 1) | ||
| if [ -z "$TAGGED" ]; then | ||
| TAGGED=$(printf '%s\n' "$REFS" | awk 'NR == 1 { print $1 }') | ||
| fi | ||
|
|
||
| # Delete existing remote tag if it exists | ||
| echo "Checking for existing remote tag: $TAG" | ||
| if git ls-remote --tags origin | grep -q "refs/tags/$TAG$"; then | ||
| echo "Deleting existing remote tag: $TAG" | ||
| if ! git push origin ":refs/tags/$TAG" 2>&1; then | ||
| echo "::warning::Failed to delete remote tag $TAG, but continuing (may not exist or insufficient permissions)" | ||
| fi | ||
| else | ||
| echo "No existing remote tag found" | ||
| if [ "$TAGGED" = "$SHA" ]; then | ||
| echo "Tag $TAG already points at $SHA; nothing to do." | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Create new tag and push | ||
| echo "Creating new tag: $TAG" | ||
| git tag -a "$TAG" -m "Nightly build $DATE" | ||
| echo "Pushing new tag: $TAG" | ||
| git push origin "$TAG" | ||
| echo "::warning::Tag $TAG already exists at $TAGGED but this build is $SHA; attempting to move it." | ||
| git tag -f -a "$TAG" -m "Nightly build $DATE" | ||
| if git push --force origin "refs/tags/$TAG"; then | ||
| echo "Moved $TAG to $SHA." | ||
| else | ||
| echo "::warning::Could not move $TAG; it is protected by an immutable release. Leaving the published tag at $TAGGED." | ||
| fi | ||
|
Comment on lines
+724
to
+728
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the checked Useful? React with 👍 / 👎. |
||
|
|
||
| - name: Publish or update nightly release | ||
| env: | ||
| VERSION: ${{ needs.check-for-changes.outputs.version }} | ||
| SHORT_SHA: ${{ needs.check-for-changes.outputs.short_sha }} | ||
| run: | | ||
| TAG="nightly-$(date +'%Y-%m-%d')" | ||
| gh release delete "$TAG" --yes 2>/dev/null || true | ||
|
|
||
| # Collect all artifact files to upload with the release | ||
| # GitHub's immutable releases require assets to be attached during creation | ||
|
|
@@ -746,6 +758,22 @@ jobs: | |
| echo "Files to upload: $ASSET_FILES" | ||
| echo "Number of files: $(echo $ASSET_FILES | wc -w)" | ||
|
|
||
| # An immutable release cannot be deleted or edited once published, so the | ||
| # old unconditional "delete, then create" could not refresh one: the | ||
| # delete was silently discarded by "|| true" and the create then failed | ||
| # because the release already existed. Only replace a release that is | ||
| # still mutable. | ||
| RELEASE_ID=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" -q '.id' 2>/dev/null || true) | ||
| if [ -n "$RELEASE_ID" ]; then | ||
| IMMUTABLE=$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" -q '.immutable' 2>/dev/null || true) | ||
| if [ "$IMMUTABLE" = "true" ]; then | ||
| echo "::warning::Release $TAG is already published and immutable, so it cannot be replaced. Skipping the GitHub release step; this run's artifacts are on DigitalOcean Spaces, which the release notes already name as the canonical download location." | ||
| exit 0 | ||
| fi | ||
| echo "Replacing the existing (mutable) release $TAG." | ||
| gh release delete "$TAG" --yes 2>/dev/null || true | ||
| fi | ||
|
Comment on lines
+766
to
+775
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 Immutability detection depends on the The skip decision hinges entirely on Was this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
+766
to
+775
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "## files"
git ls-files | rg '(^|/)(nightly\.yml|.*workflow.*\.ya?ml)$' || true
echo
echo "## nightly workflow relevant section"
if [ -f .github/workflows/nightly.yml ]; then
wc -l .github/workflows/nightly.yml
sed -n '740,795p' .github/workflows/nightly.yml | cat -n
fi
echo
echo "## search gh release create/delete/api around workflows"
rg -n 'gh (api|release delete|release create)|REMOVE_GITHUB_RELEASE|DELETE.*RELEASE|TAG=' .github/workflows/nightly.yml || trueRepository: WebFirstLanguage/wfl Length of output: 4326 Don’t swallow release-lookup failures before creating the release.
🤖 Prompt for AI Agents |
||
|
|
||
| # Create release with assets attached in a single command | ||
| # This works with immutable releases since assets are part of the creation | ||
| gh release create "$TAG" \ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: WebFirstLanguage/wfl
Length of output: 221
🏁 Script executed:
Repository: WebFirstLanguage/wfl
Length of output: 4790
🌐 Web query:
Git documentation git ls-remote exit code remote errors pattern no match💡 Result:
By default, the git ls-remote command returns an exit code of 0 if it successfully communicates with the remote repository, regardless of whether any references (refs) match your provided patterns [1][2]. To distinguish between successful communication and the absence of matching references, you must use the --exit-code flag [1][3]. When --exit-code is provided, the command will exit with status 2 if no matching references are found [1][4][2]. Regarding pattern matching, git ls-remote interprets patterns as glob patterns [2][5]. These patterns are matched against the tail of a reference name, meaning they either match the full name (e.g., refs/heads/foo) or match a component starting after a slash separator (e.g., bar matches refs/heads/bar but not refs/heads/foobar) [2][6]. If you provide multiple patterns, the command will display any reference that matches at least one of the specified patterns [2][6].
Citations:
Distinguish
git ls-remotecommunication failures from a missing nightly tag.At line 701,
REFS=$(...) || trueonly turns real command failures into “no tag found,” which can lead to pushing a new tag instead of treating a query/auth/network failure as the real result.Suggested fix
🤖 Prompt for AI Agents