Stop an expired tap token from killing the whole release - #8
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What happened to v0.2.0
The tag pushed,
testpassed, GoReleaser built and uploaded the GitHub release with 11 assets — and then: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:
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.ymlreads throughskip_upload. A dead or missing token now emits a warning naming the repo and the HTTP status and skips that tap alone:Tokens are read with
envOrDefaultrather than.Env, so an unset secret cannot fail config loading either.workflow_dispatchwith ataginput. 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 fromRELEASE_TAGinstead ofGITHUB_REF_NAME, so a dispatch names the tag it is publishing rather than the branch it was launched from.release.mode: replace. The defaultkeep-existingleaves a failed run's assets in place, so the retry cannot correct them.Verification
goreleaser checkpasses.-eq 6assertion) and the cask and scoop manifests are still generated, just not uploaded.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_TOKENandSCOOP_BUCKET_GITHUB_TOKENare 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
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.