From 68ea06b4e331cd505fdfa63a8c97eb41a5eb15da Mon Sep 17 00:00:00 2001 From: burn Date: Sun, 2 Aug 2026 07:18:44 -0400 Subject: [PATCH 1/5] fix(macos): update latest release in place --- .github/workflows/release-macos.yml | 44 +++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index d9e4b28a..8e2d9659 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -114,12 +114,44 @@ jobs: --notes-file "${{ steps.changelog.outputs.notes_file }}" # Moving pointer for a stable download URL: # releases/download/macos-latest/BurnOSX-arm64.dmg - gh release delete macos-latest --repo "${GITHUB_REPOSITORY}" --yes --cleanup-tag || true - gh release create macos-latest "${DMG}" "${ZIP}" \ - --repo "${GITHUB_REPOSITORY}" \ - --target "${GITHUB_SHA}" \ - --title "Burn for Mac (latest)" \ - --notes "Latest macOS app build — points at ${TAG}." + ACTUAL_TAG_SHA="$(git ls-remote --tags origin refs/tags/macos-latest | awk 'NR == 1 { print $1 }')" + if [ -n "${ACTUAL_TAG_SHA}" ]; then + # Updating a release's targetCommitish does not move an existing tag. + # This force update is intentionally destructive: macos-latest is a + # moving pointer, so moving its tag is the contract. The versioned + # macos-v* releases remain immutable history. + gh api --method PATCH \ + "repos/${GITHUB_REPOSITORY}/git/refs/tags/macos-latest" \ + -f "sha=${GITHUB_SHA}" \ + -F force=true >/dev/null + else + gh api --method POST \ + "repos/${GITHUB_REPOSITORY}/git/refs" \ + -f "ref=refs/tags/macos-latest" \ + -f "sha=${GITHUB_SHA}" >/dev/null + fi + ACTUAL_TAG_SHA="$(git ls-remote --tags origin refs/tags/macos-latest | awk 'NR == 1 { print $1 }')" + if [ "${ACTUAL_TAG_SHA}" != "${GITHUB_SHA}" ]; then + echo "macos-latest tag mismatch: expected ${GITHUB_SHA}, got ${ACTUAL_TAG_SHA:-}" >&2 + exit 1 + fi + + if gh release view macos-latest --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + gh release upload macos-latest "${DMG}" "${ZIP}" \ + --repo "${GITHUB_REPOSITORY}" \ + --clobber + gh release edit macos-latest \ + --repo "${GITHUB_REPOSITORY}" \ + --target "${GITHUB_SHA}" \ + --title "Burn for Mac (latest)" \ + --notes "Latest macOS app build — points at ${TAG}." + else + gh release create macos-latest "${DMG}" "${ZIP}" \ + --repo "${GITHUB_REPOSITORY}" \ + --target "${GITHUB_SHA}" \ + --title "Burn for Mac (latest)" \ + --notes "Latest macOS app build — points at ${TAG}." + fi - name: Upload build artifacts if: always() From aa1782ed6fdaf23a7c324bff7da0254790a1cb45 Mon Sep 17 00:00:00 2001 From: burn Date: Sun, 2 Aug 2026 07:33:07 -0400 Subject: [PATCH 2/5] fix(ci): report stale latest assets on upload failure --- .github/workflows/release-macos.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 8e2d9659..f3494482 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -137,9 +137,12 @@ jobs: fi if gh release view macos-latest --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then - gh release upload macos-latest "${DMG}" "${ZIP}" \ + if ! gh release upload macos-latest "${DMG}" "${ZIP}" \ --repo "${GITHUB_REPOSITORY}" \ - --clobber + --clobber; then + echo "macos-latest tag moved to ${GITHUB_SHA}, but release assets were not updated" >&2 + exit 1 + fi gh release edit macos-latest \ --repo "${GITHUB_REPOSITORY}" \ --target "${GITHUB_SHA}" \ From 58cbf2e7cc351dd49c7006c5dbf3f653182bd3c6 Mon Sep 17 00:00:00 2001 From: burn Date: Sun, 2 Aug 2026 07:56:47 -0400 Subject: [PATCH 3/5] fix(ci): serialize latest release recovery --- .github/workflows/release-macos.yml | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index f3494482..e09f53e9 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -7,6 +7,10 @@ name: Release (macOS app) on: workflow_dispatch: +concurrency: + group: release-macos + cancel-in-progress: false + permissions: contents: write @@ -105,6 +109,14 @@ jobs: # the releases feed and fetches BurnOSX-arm64.zip from the macos-v* # release it picks). ZIP="dist/BurnOSX-arm64.zip" + print_latest_recovery() { + echo "macos-latest may contain a mix of old and new assets. Do not rerun this workflow: the versioned ${TAG} release already exists." >&2 + echo "After this run finishes, recover from the retained artifact with:" >&2 + echo " gh run download ${GITHUB_RUN_ID} --repo ${GITHUB_REPOSITORY} --name agentlimit-macos-${GITHUB_RUN_ID} --dir macos-recovery" >&2 + echo " gh release upload macos-latest macos-recovery/BurnOSX-arm64.dmg macos-recovery/BurnOSX-arm64.zip --repo ${GITHUB_REPOSITORY} --clobber" >&2 + echo " gh release edit macos-latest --repo ${GITHUB_REPOSITORY} --target ${GITHUB_SHA} --title 'Burn for Mac (latest)' --notes 'Latest macOS app build — points at ${TAG}.'" >&2 + echo " test \"\$(git ls-remote --tags https://github.com/${GITHUB_REPOSITORY}.git refs/tags/macos-latest | awk 'NR == 1 { print \$1 }')\" = ${GITHUB_SHA}" >&2 + } # Versioned release (history). Not marked --latest: burn's own v* CLI # releases own the repo's "latest" pointer. gh release create "${TAG}" "${DMG}" "${ZIP}" \ @@ -140,14 +152,19 @@ jobs: if ! gh release upload macos-latest "${DMG}" "${ZIP}" \ --repo "${GITHUB_REPOSITORY}" \ --clobber; then - echo "macos-latest tag moved to ${GITHUB_SHA}, but release assets were not updated" >&2 + echo "macos-latest tag moved to ${GITHUB_SHA}, but its two assets may now be only partially updated" >&2 + print_latest_recovery exit 1 fi - gh release edit macos-latest \ + if ! gh release edit macos-latest \ --repo "${GITHUB_REPOSITORY}" \ --target "${GITHUB_SHA}" \ --title "Burn for Mac (latest)" \ - --notes "Latest macOS app build — points at ${TAG}." + --notes "Latest macOS app build — points at ${TAG}."; then + echo "macos-latest assets were replaced, but its release metadata was not updated" >&2 + print_latest_recovery + exit 1 + fi else gh release create macos-latest "${DMG}" "${ZIP}" \ --repo "${GITHUB_REPOSITORY}" \ @@ -155,6 +172,11 @@ jobs: --title "Burn for Mac (latest)" \ --notes "Latest macOS app build — points at ${TAG}." fi + ACTUAL_TAG_SHA="$(git ls-remote --tags origin refs/tags/macos-latest | awk 'NR == 1 { print $1 }')" + if [ "${ACTUAL_TAG_SHA}" != "${GITHUB_SHA}" ]; then + echo "macos-latest changed during publication: expected ${GITHUB_SHA}, got ${ACTUAL_TAG_SHA:-}" >&2 + exit 1 + fi - name: Upload build artifacts if: always() From d0af27649680483660042bb44b1e2bde64e83dc8 Mon Sep 17 00:00:00 2001 From: burn Date: Sun, 2 Aug 2026 08:35:11 -0400 Subject: [PATCH 4/5] fix(ci): make macOS release reruns idempotent --- .github/workflows/release-macos.yml | 76 +++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index e09f53e9..57fe1ffb 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -9,6 +9,7 @@ on: concurrency: group: release-macos + # A cancelled run can leave macos-latest moved with unfinished assets. cancel-in-progress: false permissions: @@ -35,13 +36,38 @@ jobs: run: | YEAR="$(date +%Y)" MONTH="$((10#$(date +%m)))" - COUNT="$(gh api --paginate "repos/${GITHUB_REPOSITORY}/releases" \ - --jq '.[].tag_name' | grep -Ec "^macos-v${YEAR}\.${MONTH}\." || true)" - N="$((COUNT + 1))" - VERSION="${YEAR}.${MONTH}.${N}" - echo "Release #${N} for ${YEAR}-$(date +%m) -> macos-v${VERSION}" + MATCHING_TAGS="$(git ls-remote --tags origin 'refs/tags/macos-v*' | \ + awk -v sha="${GITHUB_SHA}" ' + $1 == sha && $2 ~ /^refs\/tags\/macos-v[0-9]+\.[0-9]+\.[0-9]+$/ { + sub(/^refs\/tags\//, "", $2) + print $2 + } + ')" + MATCHING_COUNT="$(printf '%s\n' "${MATCHING_TAGS}" | sed '/^$/d' | wc -l | tr -d ' ')" + if [ "${MATCHING_COUNT}" -gt 1 ]; then + echo "multiple macos-v* tags already point at ${GITHUB_SHA}; refusing an ambiguous rerun:" >&2 + printf '%s\n' "${MATCHING_TAGS}" >&2 + exit 1 + fi + if [ "${MATCHING_COUNT}" -eq 1 ]; then + TAG="${MATCHING_TAGS}" + VERSION="${TAG#macos-v}" + echo "Reusing ${TAG} for ${GITHUB_SHA}; this run will repair that release instead of allocating another version." + else + PREFIX="refs/tags/macos-v${YEAR}.${MONTH}." + N="$(git ls-remote --tags origin "${PREFIX}*" | awk -v prefix="${PREFIX}" ' + index($2, prefix) == 1 { + suffix = substr($2, length(prefix) + 1) + if (suffix ~ /^[0-9]+$/ && suffix > max) max = suffix + } + END { print max + 1 } + ')" + VERSION="${YEAR}.${MONTH}.${N}" + TAG="macos-v${VERSION}" + echo "Release #${N} for ${YEAR}-$(date +%m) -> ${TAG}" + fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "tag=macos-v${VERSION}" >> "$GITHUB_OUTPUT" + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - name: Generate changelog id: changelog @@ -110,20 +136,41 @@ jobs: # release it picks). ZIP="dist/BurnOSX-arm64.zip" print_latest_recovery() { - echo "macos-latest may contain a mix of old and new assets. Do not rerun this workflow: the versioned ${TAG} release already exists." >&2 - echo "After this run finishes, recover from the retained artifact with:" >&2 + echo "macos-latest tag is ${GITHUB_SHA}. Asset replacement was attempted, but the current asset contents are indeterminate." >&2 + echo "After this run finishes, establish the current state by comparing it with the retained build artifact:" >&2 echo " gh run download ${GITHUB_RUN_ID} --repo ${GITHUB_REPOSITORY} --name agentlimit-macos-${GITHUB_RUN_ID} --dir macos-recovery" >&2 + echo " gh release download macos-latest --repo ${GITHUB_REPOSITORY} --pattern 'BurnOSX-arm64.*' --dir macos-current --clobber" >&2 + echo " shasum -a 256 macos-recovery/BurnOSX-arm64.dmg macos-current/BurnOSX-arm64.dmg" >&2 + echo " shasum -a 256 macos-recovery/BurnOSX-arm64.zip macos-current/BurnOSX-arm64.zip" >&2 + echo "To replace both assets with the retained build and restore the release metadata:" >&2 echo " gh release upload macos-latest macos-recovery/BurnOSX-arm64.dmg macos-recovery/BurnOSX-arm64.zip --repo ${GITHUB_REPOSITORY} --clobber" >&2 echo " gh release edit macos-latest --repo ${GITHUB_REPOSITORY} --target ${GITHUB_SHA} --title 'Burn for Mac (latest)' --notes 'Latest macOS app build — points at ${TAG}.'" >&2 echo " test \"\$(git ls-remote --tags https://github.com/${GITHUB_REPOSITORY}.git refs/tags/macos-latest | awk 'NR == 1 { print \$1 }')\" = ${GITHUB_SHA}" >&2 } # Versioned release (history). Not marked --latest: burn's own v* CLI # releases own the repo's "latest" pointer. - gh release create "${TAG}" "${DMG}" "${ZIP}" \ - --repo "${GITHUB_REPOSITORY}" \ - --target "${GITHUB_SHA}" \ - --title "Burn for Mac ${{ steps.version.outputs.version }}" \ - --notes-file "${{ steps.changelog.outputs.notes_file }}" + if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + VERSIONED_TAG_SHA="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 { print $1 }')" + if [ "${VERSIONED_TAG_SHA}" != "${GITHUB_SHA}" ]; then + echo "existing ${TAG} points at ${VERSIONED_TAG_SHA:-}, not ${GITHUB_SHA}; refusing to rewrite immutable history" >&2 + exit 1 + fi + echo "Repairing existing ${TAG} release for ${GITHUB_SHA}." + gh release upload "${TAG}" "${DMG}" "${ZIP}" \ + --repo "${GITHUB_REPOSITORY}" \ + --clobber + gh release edit "${TAG}" \ + --repo "${GITHUB_REPOSITORY}" \ + --target "${GITHUB_SHA}" \ + --title "Burn for Mac ${{ steps.version.outputs.version }}" \ + --notes-file "${{ steps.changelog.outputs.notes_file }}" + else + gh release create "${TAG}" "${DMG}" "${ZIP}" \ + --repo "${GITHUB_REPOSITORY}" \ + --target "${GITHUB_SHA}" \ + --title "Burn for Mac ${{ steps.version.outputs.version }}" \ + --notes-file "${{ steps.changelog.outputs.notes_file }}" + fi # Moving pointer for a stable download URL: # releases/download/macos-latest/BurnOSX-arm64.dmg ACTUAL_TAG_SHA="$(git ls-remote --tags origin refs/tags/macos-latest | awk 'NR == 1 { print $1 }')" @@ -152,7 +199,6 @@ jobs: if ! gh release upload macos-latest "${DMG}" "${ZIP}" \ --repo "${GITHUB_REPOSITORY}" \ --clobber; then - echo "macos-latest tag moved to ${GITHUB_SHA}, but its two assets may now be only partially updated" >&2 print_latest_recovery exit 1 fi @@ -186,5 +232,5 @@ jobs: path: | apps/macos/dist/*.dmg apps/macos/dist/*.zip - if-no-files-found: warn + if-no-files-found: error retention-days: 30 From e79cd4aca33afda572f30b40720bf058e4126e22 Mon Sep 17 00:00:00 2001 From: burn Date: Sun, 2 Aug 2026 10:09:49 -0400 Subject: [PATCH 5/5] fix(ci): fail closed on macOS release lookups --- .github/workflows/release-macos.yml | 64 +++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release-macos.yml b/.github/workflows/release-macos.yml index 57fe1ffb..583879ef 100644 --- a/.github/workflows/release-macos.yml +++ b/.github/workflows/release-macos.yml @@ -38,11 +38,18 @@ jobs: MONTH="$((10#$(date +%m)))" MATCHING_TAGS="$(git ls-remote --tags origin 'refs/tags/macos-v*' | \ awk -v sha="${GITHUB_SHA}" ' - $1 == sha && $2 ~ /^refs\/tags\/macos-v[0-9]+\.[0-9]+\.[0-9]+$/ { - sub(/^refs\/tags\//, "", $2) - print $2 + $2 ~ /^refs\/tags\/macos-v[0-9]+\.[0-9]+\.[0-9]+(\^\{\})?$/ { + ref = $2 + peeled = sub(/\^\{\}$/, "", ref) + sub(/^refs\/tags\//, "", ref) + if (peeled || !(ref in resolved)) resolved[ref] = $1 } - ')" + END { + for (ref in resolved) { + if (resolved[ref] == sha) print ref + } + } + ' | sort)" MATCHING_COUNT="$(printf '%s\n' "${MATCHING_TAGS}" | sed '/^$/d' | wc -l | tr -d ' ')" if [ "${MATCHING_COUNT}" -gt 1 ]; then echo "multiple macos-v* tags already point at ${GITHUB_SHA}; refusing an ambiguous rerun:" >&2 @@ -58,7 +65,7 @@ jobs: N="$(git ls-remote --tags origin "${PREFIX}*" | awk -v prefix="${PREFIX}" ' index($2, prefix) == 1 { suffix = substr($2, length(prefix) + 1) - if (suffix ~ /^[0-9]+$/ && suffix > max) max = suffix + if (suffix ~ /^[0-9]+$/ && (suffix + 0) > max) max = suffix + 0 } END { print max + 1 } ')" @@ -136,7 +143,8 @@ jobs: # release it picks). ZIP="dist/BurnOSX-arm64.zip" print_latest_recovery() { - echo "macos-latest tag is ${GITHUB_SHA}. Asset replacement was attempted, but the current asset contents are indeterminate." >&2 + local asset_state="${1:-Asset replacement was attempted, but the current asset contents are indeterminate.}" + echo "macos-latest tag is ${GITHUB_SHA}. ${asset_state}" >&2 echo "After this run finishes, establish the current state by comparing it with the retained build artifact:" >&2 echo " gh run download ${GITHUB_RUN_ID} --repo ${GITHUB_REPOSITORY} --name agentlimit-macos-${GITHUB_RUN_ID} --dir macos-recovery" >&2 echo " gh release download macos-latest --repo ${GITHUB_REPOSITORY} --pattern 'BurnOSX-arm64.*' --dir macos-current --clobber" >&2 @@ -147,10 +155,34 @@ jobs: echo " gh release edit macos-latest --repo ${GITHUB_REPOSITORY} --target ${GITHUB_SHA} --title 'Burn for Mac (latest)' --notes 'Latest macOS app build — points at ${TAG}.'" >&2 echo " test \"\$(git ls-remote --tags https://github.com/${GITHUB_REPOSITORY}.git refs/tags/macos-latest | awk 'NR == 1 { print \$1 }')\" = ${GITHUB_SHA}" >&2 } + print_latest_lookup_recovery() { + echo "macos-latest tag is ${GITHUB_SHA}. Release lookup failed before asset replacement; this run did not inspect or change the release assets." >&2 + echo "After restoring GitHub API access, download the retained build artifact:" >&2 + echo " gh run download ${GITHUB_RUN_ID} --repo ${GITHUB_REPOSITORY} --name agentlimit-macos-${GITHUB_RUN_ID} --dir macos-recovery" >&2 + echo "Then establish whether the release exists:" >&2 + echo " gh release view macos-latest --repo ${GITHUB_REPOSITORY}" >&2 + echo "If it exists, compare and replace its assets with the retained build as needed, then repair its metadata:" >&2 + echo " gh release download macos-latest --repo ${GITHUB_REPOSITORY} --pattern 'BurnOSX-arm64.*' --dir macos-current --clobber" >&2 + echo " shasum -a 256 macos-recovery/BurnOSX-arm64.dmg macos-current/BurnOSX-arm64.dmg" >&2 + echo " shasum -a 256 macos-recovery/BurnOSX-arm64.zip macos-current/BurnOSX-arm64.zip" >&2 + echo " gh release upload macos-latest macos-recovery/BurnOSX-arm64.dmg macos-recovery/BurnOSX-arm64.zip --repo ${GITHUB_REPOSITORY} --clobber" >&2 + echo " gh release edit macos-latest --repo ${GITHUB_REPOSITORY} --target ${GITHUB_SHA} --title 'Burn for Mac (latest)' --notes 'Latest macOS app build — points at ${TAG}.'" >&2 + echo "If it is confirmed absent, create it from the retained build instead:" >&2 + echo " gh release create macos-latest macos-recovery/BurnOSX-arm64.dmg macos-recovery/BurnOSX-arm64.zip --repo ${GITHUB_REPOSITORY} --target ${GITHUB_SHA} --title 'Burn for Mac (latest)' --notes 'Latest macOS app build — points at ${TAG}.'" >&2 + echo "Finally verify the moving tag directly:" >&2 + echo " test \"\$(git ls-remote --tags https://github.com/${GITHUB_REPOSITORY}.git refs/tags/macos-latest | awk 'NR == 1 { print \$1 }')\" = ${GITHUB_SHA}" >&2 + } # Versioned release (history). Not marked --latest: burn's own v* CLI # releases own the repo's "latest" pointer. if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then - VERSIONED_TAG_SHA="$(git ls-remote --tags origin "refs/tags/${TAG}" | awk 'NR == 1 { print $1 }')" + VERSIONED_TAG_SHA="$(git ls-remote --tags origin "refs/tags/${TAG}*" | awk -v ref="refs/tags/${TAG}" ' + $2 == ref { direct = $1 } + $2 == ref "^{}" { peeled = $1 } + END { + if (peeled != "") print peeled + else print direct + } + ')" if [ "${VERSIONED_TAG_SHA}" != "${GITHUB_SHA}" ]; then echo "existing ${TAG} points at ${VERSIONED_TAG_SHA:-}, not ${GITHUB_SHA}; refusing to rewrite immutable history" >&2 exit 1 @@ -195,7 +227,23 @@ jobs: exit 1 fi - if gh release view macos-latest --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then + set +e + LATEST_RELEASE_LOOKUP="$(gh api --include \ + "repos/${GITHUB_REPOSITORY}/releases/tags/macos-latest" 2>&1)" + LATEST_RELEASE_STATUS=$? + set -e + if [ "${LATEST_RELEASE_STATUS}" -eq 0 ]; then + LATEST_RELEASE_EXISTS=true + elif printf '%s\n' "${LATEST_RELEASE_LOOKUP}" | grep -Eq '^HTTP/[0-9.]+ 404([[:space:]]|$)'; then + LATEST_RELEASE_EXISTS=false + else + echo "macos-latest release lookup failed; its existence is indeterminate, so refusing to create or replace assets" >&2 + printf '%s\n' "${LATEST_RELEASE_LOOKUP}" >&2 + print_latest_lookup_recovery + exit 1 + fi + + if [ "${LATEST_RELEASE_EXISTS}" = true ]; then if ! gh release upload macos-latest "${DMG}" "${ZIP}" \ --repo "${GITHUB_REPOSITORY}" \ --clobber; then