Skip to content

Stop an expired tap token from killing the whole release - #8

Merged
adhikjoshi merged 1 commit into
mainfrom
fix/release-tap-tokens-must-not-block
Sep 9, 2026
Merged

Stop an expired tap token from killing the whole release#8
adhikjoshi merged 1 commit into
mainfrom
fix/release-tap-tokens-must-not-block

Conversation

@adhikjoshi

@adhikjoshi adhikjoshi commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What happened to v0.2.0

The tag pushed, test passed, GoReleaser built and uploaded the GitHub release with 11 assets — and then:

• error checking for default branch  projectID=ModelsLab/homebrew-tap  statusCode=401  error=... 401 Bad credentials
• error checking for default branch  projectID=ModelsLab/scoop-bucket  statusCode=401  error=... 401 Bad credentials
⨯ release failed after 1m40s

GoReleaser treats a failed cask/scoop push as fatal, so every step after it was skipped: npm never ran, and PyPI failed on a dist/pypi/ that was never built. Both tap PATs were set on 2026-02-20 and have since expired.

A credential governing two optional install channels took down both package registries.

Why this keeps happening

The file already documents this failure twice:

  • v0.1.2 — published five npm packages, tripped npm's spam heuristic on the sixth, and PyPI never ran at all.
  • v0.1.3 — the guard was on the last step of a chain, so it protected nothing.

Each fix was scoped to whichever publisher had just failed. The taps were the one nobody had got to yet.

The fix

Probe the tap tokens before GoReleaser starts. Each token is checked against its own repository; the result sets SKIP_HOMEBREW / SKIP_SCOOP, which .goreleaser.yml reads through skip_upload. A dead or missing token now emits a warning naming the repo and the HTTP status and skips that tap alone:

::warning title=Homebrew skipped::token cannot read ModelsLab/homebrew-tap (HTTP 401) — rotate the PAT and re-run this workflow with the same tag

Tokens are read with envOrDefault rather than .Env, so an unset secret cannot fail config loading either.

workflow_dispatch with a tag input. A release that fails halfway had no way to be finished — the only trigger was the tag push that had already happened, leaving a choice between moving a published tag and burning a version number. Both jobs now check out the requested tag, and the packaging steps take the version from RELEASE_TAG instead of GITHUB_REF_NAME, so a dispatch names the tag it is publishing rather than the branch it was launched from.

release.mode: replace. The default keep-existing leaves a failed run's assets in place, so the retry cannot correct them.

Verification

  • goreleaser check passes.
  • Snapshot build with both skips set: all 6 binaries (matching the workflow's -eq 6 assertion) and the cask and scoop manifests are still generated, just not uploaded.
  • The probe script was extracted from the YAML and run against a valid token (false), an invalid one (401 warning, true) and an empty one (warning, true).

Still needs a human

Both tap PATs are expired and only an account owner can mint new ones. Until HOMEBREW_TAP_GITHUB_TOKEN and SCOOP_BUCKET_GITHUB_TOKEN are rotated, Homebrew and Scoop stay on v0.1.4 — but npm, PyPI and GitHub will publish.

After this merges, v0.2.0 can be completed with Actions → Release → Run workflow → tag v0.2.0, no re-tagging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QgkQePXPha8ShvoBerrVXL


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

v0.2.0 published its GitHub release and 11 assets, then died. Both tap PATs —
minted 2026-02-20, never rotated — now answer 401, goreleaser treats a failed
cask/scoop push as fatal, and everything after it was skipped: npm never ran,
PyPI failed on a dist/ that was never built. A credential governing two
optional install channels took down both package registries.

This is the same failure the file already documents twice. v0.1.2 published
five npm packages, tripped npm's spam heuristic on the sixth, and PyPI never
ran. v0.1.3 put the guard on the last step of a chain, which protected nothing.
Each time the fix was scoped to the publisher that happened to fail. The taps
were the remaining one.

Probe both tap tokens against their repositories before goreleaser starts and
set SKIP_HOMEBREW / SKIP_SCOOP from the result. A dead or missing token now
emits a workflow warning naming the repo and the HTTP status, and skips that
tap alone. .goreleaser.yml reads those through `skip_upload`, and takes the
tokens via envOrDefault so an unset secret cannot fail config loading either.

Two supporting changes:

- workflow_dispatch with a `tag` input. A release that fails halfway had no way
  to be finished — the only trigger was the tag push that had already happened,
  so the choice was moving a published tag or burning a version number. Both
  jobs check out the requested tag, and the packaging steps take the version
  from RELEASE_TAG rather than GITHUB_REF_NAME so a dispatch names the tag it
  is publishing, not the branch it was launched from.

- release.mode: replace. The default keep-existing leaves the assets from a
  failed run in place, so a retry cannot correct them.

Verified: goreleaser check passes; a snapshot build with the skips set produces
all 6 binaries and still writes the cask and scoop manifests. The probe was run
against a valid token (false), an invalid one (401 warning, true) and an empty
one (warning, true).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QgkQePXPha8ShvoBerrVXL
@adhikjoshi
adhikjoshi merged commit 4aec03c into main Sep 9, 2026
8 checks passed
@adhikjoshi
adhikjoshi deleted the fix/release-tap-tokens-must-not-block branch September 9, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant