From abc77c4a1e2e6f19e3ebfd7c7edc3ef7771b1c96 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 10 Jun 2026 12:33:27 +0100 Subject: [PATCH] homebrew: use Releases API digest for Cask checksum The release-homebrew workflow previously delegated checksum computation to the third-party action `mjcheetham/asset-hash`, which downloads the release asset and hashes the bytes locally. GitHub has been observed to occasionally serve an HTML error page (the "unicorn" page) with a 200 status code in place of release-asset content. When that happens the local hash succeeds against the wrong bytes and an incorrect SHA-256 ends up in the Cask, which then prevents users from installing. This is exactly what happened with v2.54.0.vfs.0.2, reported in microsoft/homebrew-git#102: the recorded checksum hashed the unicorn page (8e8052a0...) rather than the `.pkg` (983dd1b1...). The GitHub Releases API itself reports the asset's SHA-256 in the `digest` field of every asset entry, computed server-side when the asset is uploaded. Read that value directly instead of recomputing locally, so a corrupted download can no longer poison the checksum. The replacement step uses `gh api` (already available on ubuntu-latest) and emits collapsible log groups with the release metadata, the full asset list (name, size, digest), and the full selected-asset record. If the API ever returns something bogus again, the workflow run will contain everything needed to diagnose it without re-running the release. Asset selection mirrors the previous regex (`git-(.*)\.pkg`); the step fails fast if zero or multiple assets match, or if the digest is missing, non-sha256, or not a 64-character hex string. Assisted-by: Claude Opus 4.7 Signed-off-by: Matthew John Cheetham --- .github/workflows/release-homebrew.yml | 83 ++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-homebrew.yml b/.github/workflows/release-homebrew.yml index e6fdb42dc6bc42..6c8ef1d63838f3 100644 --- a/.github/workflows/release-homebrew.yml +++ b/.github/workflows/release-homebrew.yml @@ -16,12 +16,83 @@ jobs: run: | echo "result=$(echo $GITHUB_REF | sed -e "s/^refs\/tags\/v//")" >>$GITHUB_OUTPUT - id: hash - name: Compute release asset hash - uses: mjcheetham/asset-hash@v1.1 - with: - asset: /git-(.*)\.pkg/ - hash: sha256 - token: ${{ secrets.GITHUB_TOKEN }} + name: Look up release asset digest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG_NAME: v${{ steps.version.outputs.result }} + # Regex (Oniguruma) used by jq's `test()` to pick the macOS + # installer asset. Kept permissive to match `git-(.*)\.pkg` from + # the previous mjcheetham/asset-hash invocation. + ASSET_PATTERN: 'git-(.*)\.pkg' + run: | + set -euo pipefail + + # GitHub has been observed to occasionally serve the unicorn + # error page with a 200 status code for release-asset downloads, + # leading to bogus checksums when the asset is hashed locally + # (see microsoft/homebrew-git#102). Use the digest reported by + # the Releases API instead, and log every intermediate value so + # any future API misbehaviour can be diagnosed from the workflow + # run alone. + + echo "::group::Fetching release metadata" + echo "Repository: $GH_REPO" + echo "Tag: $TAG_NAME" + echo "Endpoint: repos/$GH_REPO/releases/tags/$TAG_NAME" + release_json=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "repos/$GH_REPO/releases/tags/$TAG_NAME") + jq '{id, tag_name, name, html_url, draft, prerelease, + published_at, asset_count: (.assets | length)}' \ + <<<"$release_json" + echo "::endgroup::" + + echo "::group::Release assets" + jq -r '.assets[] + | "\(.id)\t\(.name)\tsize=\(.size)\tdigest=\(.digest // "")"' \ + <<<"$release_json" + echo "::endgroup::" + + echo "::group::Matching asset (pattern: $ASSET_PATTERN)" + asset_json=$(jq --arg pat "$ASSET_PATTERN" ' + [ .assets[] | select(.name | test($pat)) ] as $matches + | if ($matches | length) == 0 then + error("no asset matches pattern \($pat)") + elif ($matches | length) > 1 then + error("multiple assets match pattern \($pat): " + + ([$matches[].name] | join(", "))) + else $matches[0] end' <<<"$release_json") + jq '{id, name, label, content_type, state, size, digest, + download_count, created_at, updated_at, + browser_download_url, url}' <<<"$asset_json" + echo "::endgroup::" + + digest=$(jq -r '.digest // ""' <<<"$asset_json") + case "$digest" in + sha256:*) + sha256=${digest#sha256:} + ;; + "") + echo "::error::Asset has no 'digest' field; GitHub API may" \ + "not have populated it for this release." >&2 + exit 1 + ;; + *) + echo "::error::Asset digest is not sha256: '$digest'" >&2 + exit 1 + ;; + esac + + if ! printf '%s' "$sha256" | grep -Eq '^[0-9a-f]{64}$'; then + echo "::error::Asset digest is not a 64-char hex string:" \ + "'$sha256'" >&2 + exit 1 + fi + + echo "Asset SHA-256: $sha256" + echo "result=$sha256" >>"$GITHUB_OUTPUT" - name: Log into Azure uses: azure/login@v3 with: