Update Homebrew Tap workflow (BenedictKing/ccx)
The Update Homebrew Tap workflow from BenedictKing/ccx, explained and optimized by Latchkey.
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Update Homebrew Tap workflow from the BenedictKing/ccx repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Update Homebrew Tap
permissions:
contents: read
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish to Homebrew, for example v2.8.17"
required: true
type: string
jobs:
update-homebrew:
runs-on: ubuntu-latest
steps:
- name: Get release metadata
id: release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_TAG: ${{ inputs.tag }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
TAG="$INPUT_TAG"
else
TAG="$RELEASE_TAG"
fi
if [ -z "$TAG" ]; then
echo "Error: release tag is empty" >&2
exit 1
fi
API="https://api.github.com/repos/${{ github.repository }}/releases/tags/${TAG}"
RELEASE_JSON=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$API")
RELEASE_TAG_FOUND=$(echo "$RELEASE_JSON" | jq -r '.tag_name')
if [ "$RELEASE_TAG_FOUND" = "null" ] || [ -z "$RELEASE_TAG_FOUND" ]; then
echo "Error: release ${TAG} not found" >&2
exit 1
fi
if [ "$(echo "$RELEASE_JSON" | jq -r '.draft')" != "false" ]; then
echo "Error: release ${TAG} is still a draft" >&2
exit 1
fi
if [ "$(echo "$RELEASE_JSON" | jq -r '.prerelease')" = "true" ]; then
echo "Skipping Homebrew update for prerelease ${TAG}"
echo "skip=true" >> $GITHUB_OUTPUT
exit 0
fi
if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Error: release tag must look like vX.Y.Z, got: ${TAG}" >&2
exit 1
fi
VERSION="${TAG#v}"
echo "skip=false" >> $GITHUB_OUTPUT
echo "tag=$TAG" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "api=$API" >> $GITHUB_OUTPUT
- name: Download DMG SHA256 assets
id: sha
if: steps.release.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
API: ${{ steps.release.outputs.api }}
VERSION: ${{ steps.release.outputs.version }}
run: |
for ARCH in arm64 amd64; do
ASSET_NAME="CCX-Desktop-${VERSION}-darwin-${ARCH}.dmg.sha256"
DOWNLOAD_URL=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$API" \
| jq -r --arg name "$ASSET_NAME" '.assets[] | select(.name == $name) | .browser_download_url')
if [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" = "null" ]; then
echo "Error: asset ${ASSET_NAME} not found" >&2
exit 1
fi
SHA=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$DOWNLOAD_URL" | cut -d' ' -f1)
if ! echo "$SHA" | grep -Eq '^[a-f0-9]{64}$'; then
echo "Error: invalid SHA256 for ${ASSET_NAME}: ${SHA}" >&2
exit 1
fi
case "$ARCH" in
arm64) OUTPUT_NAME="sha_arm" ;;
amd64) OUTPUT_NAME="sha_intel" ;;
esac
echo "${OUTPUT_NAME}=${SHA}" >> $GITHUB_OUTPUT
echo "Downloaded SHA256 for ${ARCH}: ${SHA}"
done
- name: Trigger Homebrew tap update
if: steps.release.outputs.skip != 'true'
env:
VERSION: ${{ steps.release.outputs.version }}
SHA_ARM: ${{ steps.sha.outputs.sha_arm }}
SHA_INTEL: ${{ steps.sha.outputs.sha_intel }}
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
run: |
if [ -z "$TAP_TOKEN" ]; then
echo "Error: HOMEBREW_TAP_TOKEN is not configured" >&2
exit 1
fi
curl --fail-with-body -X POST \
-H "Authorization: token ${TAP_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/BenedictKing/homebrew-ccx/dispatches" \
-d "{
\"event_type\": \"update-cask\",
\"client_payload\": {
\"version\": \"${VERSION}\",
\"sha_arm\": \"${SHA_ARM}\",
\"sha_intel\": \"${SHA_INTEL}\"
}
}"
echo "Dispatched update-cask to homebrew-ccx: v${VERSION}"
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Update Homebrew Tap permissions: contents: read on: release: types: [published] workflow_dispatch: inputs: tag: description: "Release tag to publish to Homebrew, for example v2.8.17" required: true type: string jobs: update-homebrew: timeout-minutes: 30 runs-on: latchkey-small steps: - name: Get release metadata id: release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} EVENT_NAME: ${{ github.event_name }} RELEASE_TAG: ${{ github.event.release.tag_name }} INPUT_TAG: ${{ inputs.tag }} run: | if [ "$EVENT_NAME" = "workflow_dispatch" ]; then TAG="$INPUT_TAG" else TAG="$RELEASE_TAG" fi if [ -z "$TAG" ]; then echo "Error: release tag is empty" >&2 exit 1 fi API="https://api.github.com/repos/${{ github.repository }}/releases/tags/${TAG}" RELEASE_JSON=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$API") RELEASE_TAG_FOUND=$(echo "$RELEASE_JSON" | jq -r '.tag_name') if [ "$RELEASE_TAG_FOUND" = "null" ] || [ -z "$RELEASE_TAG_FOUND" ]; then echo "Error: release ${TAG} not found" >&2 exit 1 fi if [ "$(echo "$RELEASE_JSON" | jq -r '.draft')" != "false" ]; then echo "Error: release ${TAG} is still a draft" >&2 exit 1 fi if [ "$(echo "$RELEASE_JSON" | jq -r '.prerelease')" = "true" ]; then echo "Skipping Homebrew update for prerelease ${TAG}" echo "skip=true" >> $GITHUB_OUTPUT exit 0 fi if ! echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then echo "Error: release tag must look like vX.Y.Z, got: ${TAG}" >&2 exit 1 fi VERSION="${TAG#v}" echo "skip=false" >> $GITHUB_OUTPUT echo "tag=$TAG" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT echo "api=$API" >> $GITHUB_OUTPUT - name: Download DMG SHA256 assets id: sha if: steps.release.outputs.skip != 'true' env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} API: ${{ steps.release.outputs.api }} VERSION: ${{ steps.release.outputs.version }} run: | for ARCH in arm64 amd64; do ASSET_NAME="CCX-Desktop-${VERSION}-darwin-${ARCH}.dmg.sha256" DOWNLOAD_URL=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$API" \ | jq -r --arg name "$ASSET_NAME" '.assets[] | select(.name == $name) | .browser_download_url') if [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" = "null" ]; then echo "Error: asset ${ASSET_NAME} not found" >&2 exit 1 fi SHA=$(curl -sL -H "Authorization: token ${GH_TOKEN}" "$DOWNLOAD_URL" | cut -d' ' -f1) if ! echo "$SHA" | grep -Eq '^[a-f0-9]{64}$'; then echo "Error: invalid SHA256 for ${ASSET_NAME}: ${SHA}" >&2 exit 1 fi case "$ARCH" in arm64) OUTPUT_NAME="sha_arm" ;; amd64) OUTPUT_NAME="sha_intel" ;; esac echo "${OUTPUT_NAME}=${SHA}" >> $GITHUB_OUTPUT echo "Downloaded SHA256 for ${ARCH}: ${SHA}" done - name: Trigger Homebrew tap update if: steps.release.outputs.skip != 'true' env: VERSION: ${{ steps.release.outputs.version }} SHA_ARM: ${{ steps.sha.outputs.sha_arm }} SHA_INTEL: ${{ steps.sha.outputs.sha_intel }} TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} run: | if [ -z "$TAP_TOKEN" ]; then echo "Error: HOMEBREW_TAP_TOKEN is not configured" >&2 exit 1 fi curl --fail-with-body -X POST \ -H "Authorization: token ${TAP_TOKEN}" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/BenedictKing/homebrew-ccx/dispatches" \ -d "{ \"event_type\": \"update-cask\", \"client_payload\": { \"version\": \"${VERSION}\", \"sha_arm\": \"${SHA_ARM}\", \"sha_intel\": \"${SHA_INTEL}\" } }" echo "Dispatched update-cask to homebrew-ccx: v${VERSION}"
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Network fetches
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.