Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 169 additions & 18 deletions .github/workflows/release-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ name: Release (macOS app)
on:
workflow_dispatch:

concurrency:
group: release-macos
# A cancelled run can leave macos-latest moved with unfinished assets.
cancel-in-progress: false

permissions:
contents: write

Expand All @@ -31,13 +36,45 @@ 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}" '
$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
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 + 0) > max) max = suffix + 0
}
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
Expand Down Expand Up @@ -105,21 +142,135 @@ jobs:
# the releases feed and fetches BurnOSX-arm64.zip from the macos-v*
# release it picks).
ZIP="dist/BurnOSX-arm64.zip"
print_latest_recovery() {
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
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
}
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.
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 -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:-<missing>}, 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}" \
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
--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
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:-<missing>}" >&2
exit 1
fi

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
print_latest_recovery
exit 1
fi
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}."; 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}" \
--target "${GITHUB_SHA}" \
--title "Burn for Mac (latest)" \
--notes "Latest macOS app build — points at ${TAG}."
fi
Comment on lines +262 to +268

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Release publishing fails when the moving download tag exists but its release was removed

When the moving "latest" entry is missing but its tag still exists, the workflow creates the entry against the stale tag (gh release create macos-latest at .github/workflows/release-macos.yml:143-147) without moving that tag first, so the stable download link keeps pointing at an old build and the job then fails.
Impact: A release run can end in failure with the public "latest" download still serving the previous build.

Why the else branch can hit a pre-existing tag

The previous implementation removed both release and tag (gh release delete macos-latest --yes --cleanup-tag), so a create always started clean. The new else branch is chosen purely on gh release view macos-latest failing (.github/workflows/release-macos.yml:125). If the release was deleted manually (or a prior run failed after the tag existed), the tag macos-latest remains. gh release create with --target will reuse the existing tag and will not move it, so verify_macos_latest_tag (.github/workflows/release-macos.yml:117-124) reports a mismatch and the step exits non-zero, leaving a release whose assets are attached to an old commit's tag. Handling this would mean force-updating (or deleting) the ref in the else branch too when the tag already exists.

Prompt for agents
In .github/workflows/release-macos.yml, the else branch (release does not exist) assumes the macos-latest tag also does not exist. If the tag exists but the release was removed, `gh release create macos-latest --target ${GITHUB_SHA}` reuses the stale tag without moving it, so verify_macos_latest_tag fails and the stable download pointer stays on the old commit. Consider probing for the existing ref (e.g. `git ls-remote --tags origin refs/tags/macos-latest` or `gh api .../git/ref/tags/macos-latest`) and force-updating/deleting the ref before creating the release in this branch.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at 68ea06b4e331cd505fdfa63a8c97eb41a5eb15da. Tag state is now handled before release state: an absent ref is created, an existing ref is force-moved, and git ls-remote --tags must equal GITHUB_SHA before either release upload/edit or release create runs. Verified the exact orphan-tag path live with a unique scratch tag: tag-only A → PATCH B → ls-remote B → create release → ls-remote remained B; scratch artifacts removed.

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:-<missing>}" >&2
exit 1
fi

- name: Upload build artifacts
if: always()
Expand All @@ -129,5 +280,5 @@ jobs:
path: |
apps/macos/dist/*.dmg
apps/macos/dist/*.zip
if-no-files-found: warn
if-no-files-found: error
retention-days: 30
Loading