Skip to content

Rotate HOMEBREW_TAP_TOKEN: the secret is dead and blocks unattended releases #84

Closed

Description

@jamesainslie

The v0.6.6 release run failed with '401 Bad credentials' from goreleaser, meaning the HOMEBREW_TAP_TOKEN repository secret (used as goreleaser's GITHUB_TOKEN in .github/workflows/release.yml) is expired or revoked. No release workflow had run since v0.6.0, so this went unnoticed; releases v0.6.1 through v0.6.5 are bare tags with no binaries.

v0.6.6 was published manually (goreleaser run locally with an operator token) after creating the previously missing yaklabco/homebrew-dot tap repository. Formula/dot.rb now exists there at 0.6.6.

Action needed (requires an interactive PAT creation, so cannot be automated):

  1. Create a token with contents write access to yaklabco/dot and yaklabco/homebrew-dot.
  2. gh secret set HOMEBREW_TAP_TOKEN --repo yaklabco/dot
  3. Test with: gh workflow run release.yml -f tag= (workflow_dispatch recovery added in ci: make the release workflow triggerable and its lint gate deterministic #82).

Optional: backfill binaries for v0.6.1 through v0.6.5 by dispatching the release workflow per tag once the secret works, though goreleaser rebuilds from the tag so older tags will build with whatever toolchain go.mod pins.

Activity

  1. jamesainslie commented on Jul 28, 2026

    @jamesainslie
    CollaboratorAuthor

    The live tap token was in pass all along (yaklab.org/homebrew-tap-token); verified it still has push permission on yaklabco/homebrew-dot and re-set the HOMEBREW_TAP_TOKEN Actions secret from it (piped pass -> gh secret set, value never displayed). Leaving this open until the next real release run proves the workflow_dispatch path end to end; the v0.7.0 release will be that test.

  2. jamesainslie commented on Aug 12, 2026

    @jamesainslie
    CollaboratorAuthor

    HOMEBREW_TAP_TOKEN rotated 2026-08-12: replaced the dead secret with a fine-grained PAT verified against the API to hold push+admin on both yaklabco/dot and yaklabco/homebrew-dot. The unattended workflow_dispatch release path remains unproven until the next release runs; keeping this open until v0.7.0 ships through it. The optional binary backfill for v0.6.1-v0.6.5 also remains outstanding.

  3. jamesainslie commented on Aug 12, 2026

    @jamesainslie
    CollaboratorAuthor

    Token choice verified by differential probes: both stored candidates pass the exact compare call that 401d in the last release run, but the org-scoped PAT also holds yaklabco org administration, so the least-privileged repo-scoped PAT stays as HOMEBREW_TAP_TOKEN. Note: it expires 2026-12-30, so re-rotation is due late December.

  4. jamesainslie commented on Aug 12, 2026

    @jamesainslie
    CollaboratorAuthor

    Status after the v0.7.0 attempt (2026-08-12): the rotated fine-grained PAT gets further than the dead secret did, but the workflow_dispatch release run still failed at publish with PATCH /repos/yaklabco/dot/releases/...: 403 Resource not accessible by personal access token.

    Root cause of the bad earlier verification: GET /repos/{owner}/{repo} returns the authenticated user s role-derived permissions, not the fine-grained token s own grants, so the push+admin probe proved nothing about the token. The changelog compare succeeded because the token has Contents: read; it evidently lacks Contents: write, which the release PATCH, asset uploads, and the tap formula push all require.

    Fix (needs the token owner, in GitHub settings): edit the fine-grained PAT to grant Contents: Read and write for both yaklabco/dot and yaklabco/homebrew-dot (or mint a classic PAT with repo scope) and update the HOMEBREW_TAP_TOKEN secret. Then verify with a real write (a no-op release PATCH), not the /repos permissions field.

    v0.7.0 itself shipped via local goreleaser (11 assets, tap formula updated, checksums verified), so this issue stays open solely for the unattended path. The workflow_dispatch trigger itself works; two runs today also flaked with socket hang up downloading tooling before reaching goreleaser, cleared by gh run rerun --failed.

  5. jamesainslie commented on Aug 12, 2026

    @jamesainslie
    CollaboratorAuthor

    Resolved 2026-08-12. HOMEBREW_TAP_TOKEN now holds the purpose-built homebrew-tap-goreleaser fine-grained token (expires 2027-03-02), scoped to yaklabco/dot + yaklabco/homebrew-tap with Contents: Read and write.

    Verified by real writes, not the misleading GET /repos permissions field:

    • no-op release PATCH on the v0.7.0 release: 200
    • blob create on yaklabco/dot: 201
    • blob create on yaklabco/homebrew-tap: 201
    • changelog compare v0.6.6...v0.7.0: 200

    That is every operation goreleaser performs (release publish, asset upload, formula push, changelog), so the workflow_dispatch release path should now run unattended; the next release proves it live. Note the tap moved to yaklabco/homebrew-tap in #99 and homebrew-dot is archived.

    History for the record: the original failure chain was (1) the secret held a dead value, (2) an interim rotation used a token whose fine-grained grants lacked Contents: write (its apparent push+admin came from the user role, not the token), and (3) the purpose-built token had only ever been granted homebrew-tap while the config pointed at homebrew-dot. All three are now aligned.

    The optional binary backfill for the bare v0.6.1-v0.6.5 tags remains undone; open a separate issue if wanted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions