From 7c24ae847a72ffa44248a278179a61fddc6c23af Mon Sep 17 00:00:00 2001 From: Aric Camarata Date: Sat, 12 Sep 2026 11:32:14 -0400 Subject: [PATCH] fix(ci): probe the dispatch token against both target repos, not just the tap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two dispatch steps in the publish job share one secret, HOMEBREW_TAP_TOKEN, but target different repos: nself-org/homebrew-nself and nself-org/admin. The preflight probe only checked homebrew-nself. So a PAT scoped to the tap but not to admin passed the gate, fired the tap dispatch, and then failed on the admin dispatch — after the release had already published. The gate reported the token as good while half of what it authorises was unusable. The probe now checks every repo it dispatches to and names the failing one in the warning. Whoever mints the replacement PAT needs repo scope on BOTH repos; this proves it before either dispatch runs rather than after one has. Verified by simulation: both-200 passes; the current both-401 state fails; and the tap-only PAT case (200 on homebrew-nself, 404 on admin) — the gap this closes — now fails instead of half-succeeding. Context: the admin side of that dispatch had no listener at all until nself-org/admin#104. HOMEBREW_TAP_TOKEN itself is still expired (HTTP 401) and needs an owner to mint a new one. --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index baf11d3a..995eb0b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -541,16 +541,30 @@ jobs: echo "::warning title=Cross-repo dispatch skipped::HOMEBREW_TAP_TOKEN secret is not configured in nself-org/cli — homebrew tap and admin Docker rebuild were NOT triggered for ${{ github.ref_name }}. Formula correctness is enforced by verify-homebrew-lockstep. Add HOMEBREW_TAP_TOKEN to enable automatic post-release dispatch." exit 0 fi - # Probe the token against the target repo with a lightweight authenticated - # GET. A 200 means the token is valid and has at least read access. - # 401 or 403 means the token is present but invalid/expired/lacks permission. - # We use curl -o /dev/null so no repo data is printed; only the HTTP status - # matters. The token is passed via step env: (valid context) — never in if:. - HTTP_STATUS=$(curl -s -o /dev/null -w '%{http_code}' \ - -H "Authorization: Bearer ${TAP_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/nself-org/homebrew-nself) + # Probe the token against EVERY repo it dispatches to, with a lightweight + # authenticated GET. A 200 means the token is valid and has at least read + # access. 401 or 403 means the token is present but invalid/expired/lacks + # permission. We use curl -o /dev/null so no repo data is printed; only the + # HTTP status matters. The token is passed via step env: (valid context) — + # never in if:. + # + # Both dispatch steps below use this one token but target DIFFERENT repos. + # Probing only homebrew-nself meant a PAT scoped to the tap but not to admin + # passed the gate, fired the tap dispatch, and then failed on the admin + # dispatch after the release had already published. Whoever mints the PAT + # must give it repo scope on BOTH repos, and this probe now proves it. + HTTP_STATUS=200 + for REPO in nself-org/homebrew-nself nself-org/admin; do + STATUS=$(curl -s -o /dev/null -w '%{http_code}' \ + -H "Authorization: Bearer ${TAP_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${REPO}") + echo "probe ${REPO}: HTTP ${STATUS}" + if [ "${STATUS}" != "200" ]; then + HTTP_STATUS="${STATUS} on ${REPO}" + fi + done if [ "${HTTP_STATUS}" = "200" ]; then echo "tap_token_ok=true" >> "$GITHUB_OUTPUT" else