fix(ci): make the Homebrew/Winget bump workflows actually fire

All four bump workflows triggered on `release: types: [released]`, which never
fires here: create-release.yml publishes the release with GITHUB_TOKEN, and
GitHub suppresses workflow-triggering events for GITHUB_TOKEN actions. They had
zero runs across every release up to v1.13.1.

Switch them to `workflow_run` on "Create Release Assets" completion, filtered to
a successful tag push. That also removes a latent race: `released` fired while
the matrix legs were still uploading assets, whereas workflow_run fires after
all of them finish.

The workflow_run payload carries no draft/prerelease flags, so add a
resolve-release composite action that reads them back from the API and feeds
assert-stable-release, keeping the defense-in-depth guard intact instead of
inferring stability from the tag string alone.

Also gate the cask/winget bumps on the package existing upstream. Neither
`amethyst-nostr` nor `VitorPamplona.Amethyst` has been bootstrapped, and
bump-cask-pr/winget-releaser can only update an existing package — without the
gate, fixing the trigger would file a spurious [release-ops] issue on every
release.

Docs: correct the claims this uncovered — Homebrew/Winget are not shipping,
macOS is arm64-only (no Intel DMG), the release carries 31 assets (13 Android,
not 12), Maven Central publishes from a step inside deploy-android and lags
repo1 by tens of minutes, RELEASE_NOTES_ID is minor-releases-only, and note the
git-credential-manager hang that blocks the release push.
This commit is contained in:
Vitor Pamplona
2026-07-29 09:37:24 -04:00
parent 7d26c06fe9
commit 054dffd55d
7 changed files with 358 additions and 102 deletions
+35 -25
View File
@@ -19,9 +19,14 @@ name: Bump Homebrew Formula (amy CLI)
# auto-bump here (symmetric to the cask action in bump-homebrew.yml) — see the
# "TODO(bootstrap)" note at the bottom of this file.
# Trigger: after "Create Release Assets" succeeds for a tag push. NOT
# `release: types: [released]` — that event never fires, because the release is
# created by create-release.yml under GITHUB_TOKEN and GitHub suppresses
# workflow-triggering events for it. See the full note in bump-homebrew.yml.
on:
release:
types: [released]
workflow_run:
workflows: ["Create Release Assets"]
types: [completed]
workflow_dispatch:
inputs:
tag:
@@ -38,44 +43,49 @@ permissions:
concurrency:
# Serialize per tag; do not cancel in-progress runs.
group: bump-homebrew-formula-${{ github.event.release.tag_name || inputs.tag }}
group: bump-homebrew-formula-${{ github.event.workflow_run.head_branch || inputs.tag }}
cancel-in-progress: false
jobs:
sync-formula:
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
# See bump-homebrew.yml for why these three conditions: successful, tag-push
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v'))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Resolve release
id: rel
uses: ./.github/actions/resolve-release
with:
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Re-assert stable release
uses: ./.github/actions/assert-stable-release
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
is_draft: ${{ github.event.release.draft || 'false' }}
- name: Resolve version
id: ver
run: |
set -euo pipefail
TAG="${{ github.event.release.tag_name || inputs.tag }}"
VER="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "ver=$VER" >> "$GITHUB_OUTPUT"
tag: ${{ steps.rel.outputs.tag }}
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
is_draft: ${{ steps.rel.outputs.is_draft }}
- name: Download jvm bundle and compute sha256
id: asset
run: |
set -euo pipefail
TAG="${{ steps.ver.outputs.tag }}"
VER="${{ steps.ver.outputs.ver }}"
TAG="${{ steps.rel.outputs.tag }}"
VER="${{ steps.rel.outputs.ver }}"
URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/amy-${VER}-jvm.tar.gz"
echo "Fetching $URL"
# The `released` event can fire a hair before every matrix leg finishes
# uploading; retry with backoff (mirrors the repo's push/pull retry ethos).
# workflow_run fires only after every upload leg has finished, so the
# asset should already be there. Retry anyway for release-CDN
# propagation (mirrors the repo's push/pull retry ethos).
ok=0
for i in 1 2 3 4 5; do
if curl -fsSL -o amy-jvm.tar.gz "$URL"; then ok=1; break; fi
@@ -110,13 +120,13 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
base: main
branch: chore/bump-amy-formula-${{ steps.ver.outputs.tag }}
branch: chore/bump-amy-formula-${{ steps.rel.outputs.tag }}
add-paths: cli/packaging/homebrew/amy.rb
commit-message: 'chore: sync amy Homebrew formula to ${{ steps.ver.outputs.tag }}'
title: 'chore: sync amy Homebrew formula to ${{ steps.ver.outputs.tag }}'
commit-message: 'chore: sync amy Homebrew formula to ${{ steps.rel.outputs.tag }}'
title: 'chore: sync amy Homebrew formula to ${{ steps.rel.outputs.tag }}'
body: |
Auto-synced `cli/packaging/homebrew/amy.rb` to the
`${{ steps.ver.outputs.tag }}` release:
`${{ steps.rel.outputs.tag }}` release:
- `url` -> `${{ steps.asset.outputs.url }}`
- `sha256` -> `${{ steps.asset.outputs.sha256 }}`
@@ -136,7 +146,7 @@ jobs:
uses: actions/github-script@v9
with:
script: |
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.create({
owner: context.repo.owner,
@@ -17,9 +17,14 @@ name: Bump Homebrew Formula (geode relay)
# human-reviewed new-formula PR (the one-time bootstrap). Once it lands, wire the
# auto-bump here — see the "TODO(bootstrap)" note at the bottom of this file.
# Trigger: after "Create Release Assets" succeeds for a tag push. NOT
# `release: types: [released]` — that event never fires, because the release is
# created by create-release.yml under GITHUB_TOKEN and GitHub suppresses
# workflow-triggering events for it. See the full note in bump-homebrew.yml.
on:
release:
types: [released]
workflow_run:
workflows: ["Create Release Assets"]
types: [completed]
workflow_dispatch:
inputs:
tag:
@@ -36,44 +41,49 @@ permissions:
concurrency:
# Serialize per tag; do not cancel in-progress runs.
group: bump-homebrew-geode-formula-${{ github.event.release.tag_name || inputs.tag }}
group: bump-homebrew-geode-formula-${{ github.event.workflow_run.head_branch || inputs.tag }}
cancel-in-progress: false
jobs:
sync-formula:
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
# See bump-homebrew.yml for why these three conditions: successful, tag-push
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v'))
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Resolve release
id: rel
uses: ./.github/actions/resolve-release
with:
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Re-assert stable release
uses: ./.github/actions/assert-stable-release
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
is_draft: ${{ github.event.release.draft || 'false' }}
- name: Resolve version
id: ver
run: |
set -euo pipefail
TAG="${{ github.event.release.tag_name || inputs.tag }}"
VER="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "ver=$VER" >> "$GITHUB_OUTPUT"
tag: ${{ steps.rel.outputs.tag }}
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
is_draft: ${{ steps.rel.outputs.is_draft }}
- name: Download jvm bundle and compute sha256
id: asset
run: |
set -euo pipefail
TAG="${{ steps.ver.outputs.tag }}"
VER="${{ steps.ver.outputs.ver }}"
TAG="${{ steps.rel.outputs.tag }}"
VER="${{ steps.rel.outputs.ver }}"
URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/geode-${VER}-jvm.tar.gz"
echo "Fetching $URL"
# The `released` event can fire a hair before every matrix leg finishes
# uploading; retry with backoff (mirrors the repo's push/pull retry ethos).
# workflow_run fires only after every upload leg has finished, so the
# asset should already be there. Retry anyway for release-CDN
# propagation (mirrors the repo's push/pull retry ethos).
ok=0
for i in 1 2 3 4 5; do
if curl -fsSL -o geode-jvm.tar.gz "$URL"; then ok=1; break; fi
@@ -108,13 +118,13 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
base: main
branch: chore/bump-geode-formula-${{ steps.ver.outputs.tag }}
branch: chore/bump-geode-formula-${{ steps.rel.outputs.tag }}
add-paths: geode/packaging/homebrew/geode.rb
commit-message: 'chore: sync geode Homebrew formula to ${{ steps.ver.outputs.tag }}'
title: 'chore: sync geode Homebrew formula to ${{ steps.ver.outputs.tag }}'
commit-message: 'chore: sync geode Homebrew formula to ${{ steps.rel.outputs.tag }}'
title: 'chore: sync geode Homebrew formula to ${{ steps.rel.outputs.tag }}'
body: |
Auto-synced `geode/packaging/homebrew/geode.rb` to the
`${{ steps.ver.outputs.tag }}` release:
`${{ steps.rel.outputs.tag }}` release:
- `url` -> `${{ steps.asset.outputs.url }}`
- `sha256` -> `${{ steps.asset.outputs.sha256 }}`
@@ -134,7 +144,7 @@ jobs:
uses: actions/github-script@v9
with:
script: |
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.create({
owner: context.repo.owner,
+59 -12
View File
@@ -1,11 +1,25 @@
name: Bump Homebrew Cask
# Fires when a GH Release is published (not draft, not prerelease).
# `release.types: [released]` event fires only for stable releases — still
# double-checked by .github/actions/assert-stable-release for defense-in-depth.
# Fires after "Create Release Assets" finishes successfully for a tag push.
#
# NOT `release: types: [released]`. That event never fires here: the release is
# created by create-release.yml using GITHUB_TOKEN, and GitHub deliberately does
# not raise workflow-triggering events for actions taken by GITHUB_TOKEN (the
# recursion guard). This workflow sat silently dead through every release up to
# v1.13.1 for exactly that reason.
#
# `workflow_run` has no such restriction, and it is also strictly better timed:
# it fires once ALL upload legs have finished, whereas `released` fired while
# assets were still uploading (hence the download retry loops in the sibling
# formula workflows).
#
# Draft/prerelease state is not in the workflow_run payload, so it is read back
# from the API by .github/actions/resolve-release and enforced by
# .github/actions/assert-stable-release.
on:
release:
types: [released]
workflow_run:
workflows: ["Create Release Assets"]
types: [completed]
workflow_dispatch:
inputs:
tag:
@@ -22,39 +36,72 @@ permissions:
concurrency:
# Serialize bumps per tag; do not cancel in-progress bumps.
group: bump-homebrew-${{ github.event.release.tag_name || inputs.tag }}
group: bump-homebrew-${{ github.event.workflow_run.head_branch || inputs.tag }}
cancel-in-progress: false
jobs:
bump:
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
# On workflow_run, filter down to a SUCCESSFUL TAG build. `event == 'push'`
# excludes create-release.yml's workflow_dispatch dry-runs (which carry a
# synthetic test_tag and publish nothing), and the head_branch prefix keeps
# non-tag runs out. Exact tag format is still enforced downstream.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v'))
runs-on: ubuntu-latest # brew runs on Linux — saves macOS runner quota
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Resolve release
id: rel
uses: ./.github/actions/resolve-release
with:
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Re-assert stable release
uses: ./.github/actions/assert-stable-release
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
is_draft: ${{ github.event.release.draft || 'false' }}
tag: ${{ steps.rel.outputs.tag }}
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
is_draft: ${{ steps.rel.outputs.is_draft }}
# `brew bump-cask-pr` can only bump a cask that ALREADY EXISTS in the tap.
# `amethyst-nostr` has never been submitted to Homebrew/homebrew-cask, so
# until the one-time bootstrap PR lands (BUILDING.md § Bootstrap) this
# workflow must no-op rather than fail — otherwise every single release
# files a spurious [release-ops] issue.
- name: Check the cask exists in homebrew-cask
id: cask
run: |
set -euo pipefail
if curl -fsSL -o /dev/null https://formulae.brew.sh/api/cask/amethyst-nostr.json; then
echo "bootstrapped=true" >> "$GITHUB_OUTPUT"
echo "cask amethyst-nostr found in homebrew-cask; proceeding with bump"
else
echo "bootstrapped=false" >> "$GITHUB_OUTPUT"
echo "::warning::Cask 'amethyst-nostr' is not in Homebrew/homebrew-cask yet, so there is nothing to bump for ${{ steps.rel.outputs.tag }}. Amethyst is NOT shipping via Homebrew. Submit the one-time new-cask PR (BUILDING.md § Bootstrap) to activate this channel."
fi
- name: Bump cask (push-or-update PR)
if: steps.cask.outputs.bootstrapped == 'true'
uses: macauley/action-homebrew-bump-cask@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
token: ${{ secrets.HOMEBREW_TOKEN }}
tap: homebrew/cask
cask: amethyst-nostr
tag: ${{ github.event.release.tag_name || inputs.tag }}
tag: ${{ steps.rel.outputs.tag }}
- name: Report failure
if: failure()
uses: actions/github-script@v9
with:
script: |
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.create({
owner: context.repo.owner,
+49 -9
View File
@@ -1,8 +1,16 @@
name: Bump Winget Manifest
# Fires after "Create Release Assets" finishes successfully for a tag push.
#
# NOT `release: types: [released]`. That event never fires here: the release is
# created by create-release.yml using GITHUB_TOKEN, and GitHub does not raise
# workflow-triggering events for GITHUB_TOKEN actions. This workflow sat
# silently dead through every release up to v1.13.1 for exactly that reason.
# See the longer note in bump-homebrew.yml.
on:
release:
types: [released]
workflow_run:
workflows: ["Create Release Assets"]
types: [completed]
workflow_dispatch:
inputs:
tag:
@@ -18,30 +26,62 @@ permissions:
issues: write
concurrency:
group: bump-winget-${{ github.event.release.tag_name || inputs.tag }}
group: bump-winget-${{ github.event.workflow_run.head_branch || inputs.tag }}
cancel-in-progress: false
jobs:
bump:
if: github.event_name == 'workflow_dispatch' || github.event.release.prerelease == false
# See bump-homebrew.yml for why these three conditions: successful, tag-push
# (not a dry-run dispatch), v-prefixed. Exact format enforced downstream.
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
startsWith(github.event.workflow_run.head_branch, 'v'))
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Resolve release
id: rel
uses: ./.github/actions/resolve-release
with:
tag: ${{ github.event.workflow_run.head_branch || inputs.tag }}
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Re-assert stable release
uses: ./.github/actions/assert-stable-release
with:
tag: ${{ github.event.release.tag_name || inputs.tag }}
is_prerelease: ${{ github.event.release.prerelease || 'false' }}
is_draft: ${{ github.event.release.draft || 'false' }}
tag: ${{ steps.rel.outputs.tag }}
is_prerelease: ${{ steps.rel.outputs.is_prerelease }}
is_draft: ${{ steps.rel.outputs.is_draft }}
# winget-releaser UPDATES an existing package; `VitorPamplona.Amethyst`
# has never been submitted to microsoft/winget-pkgs. Until the one-time
# new-package PR lands (BUILDING.md § Bootstrap), no-op instead of failing
# every release with a spurious [release-ops] issue.
- name: Check the package exists in winget-pkgs
id: pkg
shell: bash
run: |
set -euo pipefail
URL=https://api.github.com/repos/microsoft/winget-pkgs/contents/manifests/v/VitorPamplona/Amethyst
if curl -fsSL -o /dev/null -H "Accept: application/vnd.github+json" "$URL"; then
echo "bootstrapped=true" >> "$GITHUB_OUTPUT"
echo "VitorPamplona.Amethyst found in winget-pkgs; proceeding with submission"
else
echo "bootstrapped=false" >> "$GITHUB_OUTPUT"
echo "::warning::Package 'VitorPamplona.Amethyst' is not in microsoft/winget-pkgs yet, so there is nothing to update for ${{ steps.rel.outputs.tag }}. Amethyst is NOT shipping via Winget. Submit the one-time new-package PR (BUILDING.md § Bootstrap) to activate this channel."
fi
- name: Submit manifest to winget-pkgs
if: steps.pkg.outputs.bootstrapped == 'true'
uses: vedantmgoyal9/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
with:
identifier: VitorPamplona.Amethyst
version: ${{ github.event.release.tag_name || inputs.tag }}
version: ${{ steps.rel.outputs.tag }}
# Asset naming contract: scripts/asset-name.sh
installers-regex: '^amethyst-desktop-.*-windows-x64\.msi$'
token: ${{ secrets.WINGET_TOKEN }}
@@ -51,7 +91,7 @@ jobs:
uses: actions/github-script@v9
with:
script: |
const tag = context.payload.release?.tag_name || context.payload.inputs?.tag || 'unknown';
const tag = context.payload.workflow_run?.head_branch || context.payload.inputs?.tag || 'unknown';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.create({
owner: context.repo.owner,