{{ message }}
Allow downloading release assets without authentication#13723
Draft
BagToad wants to merge 3 commits into
Draft
Conversation
FetchRelease races a published-release REST lookup against a draft-release GraphQL lookup, and returned the first result unless it was ErrReleaseNotFound. A failing draft lookup, such as a 403 when unauthenticated, could mask a release the published lookup found. Prefer a found release and only error when both fail. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Downloading assets from a public repository's release works unauthenticated over REST, so drop the login gate. A token is still used when present. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request updates gh release download to work without requiring authentication for public repositories, and adjusts release lookup behavior to avoid a failed draft (GraphQL) lookup masking a successful published (REST) lookup.
Changes:
- Bypass the pre-execution login gate for
gh release download. - Update
shared.FetchReleaseselection logic to prefer a successful lookup result over an error from the other concurrent lookup. - Add a regression test ensuring an unauthorized draft lookup does not prevent downloading a published release by tag.
Show a summary per file
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 3/3 changed files
- Comments generated: 1
Member
Author
andyfeller
reviewed
Jun 25, 2026
andyfeller
left a comment
Contributor
There was a problem hiding this comment.
The only suggestion I'd like to offer is making this abundantly clear in the command description with very huge warning about the potential impact of using unauthenticated release assets with a link to GH docs page + section on unauthentiated rate limits
EnableRepoOverride's hook shadows the root auth gate, then re-runs the nearest ancestor hook to restore it. That re-run passed the ancestor as the command, so the gate judged the wrong node and ignored a leaf's DisableAuthCheck. Pass the invoked leaf instead, as cobra does for every persistent hook. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Fixes #2680
Description
gh release downloadnow works without authentication against public repositories, matchinggh extension install. A token is still used when one is present.Key Points
This change has three parts, from simplest to subtlest:
download.Why drop the auth gate
Release endpoints for public repositories are readable anonymously over REST, so the login gate is unnecessary. Removing it is the same one-line
cmdutil.DisableAuthCheckchange that #13176 made forgh extension install.The same logic here applies as with
gh extension install:In
gh release download- people are going to justcurlthe endpoint directly, and that degrades their experience since we provide a lot of abstractions over the API that makeghmuch more than acurl/gh apicall.But I'll broaden the conversation - we got feedback that folks are using
gh release downloadmore in CI and they want to secure their CI by not providinggha token unless it's necessary. If you're downloading a public asset, that's not necessary and we force you to use a larger than necessary token scope.Fixing the by-tag race to be success oriented
The by-tag path creates problems that make this deviate a bit from
gh ext install- that's one reason why this diff is a bit bigger.FetchReleaseraces a published-release REST lookup against a draft-release GraphQL lookup. GraphQL rejects anonymous requests, so the draft lookup returns a 403. The old selector returned the first result unless it was exactlyErrReleaseNotFound, so that 403 could win the race and mask a release the REST lookup found.The selector now prefers a found release and only errors when both lookups fail. We used to return whichever result arrived first, treating only a not-found error as a reason to wait for the second:
Now we take a success from either lookup, and only surface an error when both fail:
This also stops a transient draft-lookup failure from masking a real release for authenticated users.
Honoring the gate under repo override
Removing the gate did not take effect on its own.
releaseenables the-R/--repooverride, and that installs aPersistentPreRunEon thereleasecommand. cobra runs only the nearest such hook walking up from the command you invoked, so the override hook shadows the root auth gate. The override re-runs the nearest ancestor hook to make up for that, but it handed the ancestor to the hook as the command, so the auth gate judgedreleaseinstead ofdownloadand never saw download'sDisableAuthCheck.The fix keeps the invoked command and passes that leaf up, the node cobra would have judged if the repo override didn't muck with it:
This mirrors cobra's own
executeloop, which walks the parents but always hands them the leaf command. cobra'sEnableTraverseRunHooksglobal would run the whole chain for us and maybe is a better long term fix, but it is process-wide and would change pre-run behavior for every command, so I'm not touching it here.I guarded this with an integration test, since the bug only surfaces with the pieces wired together.
Test_EnableRepoOverride_authCheckIntegrationbuilds a root gate, a repo-override parent, and a leaf, then asserts the gate judged the leaf: opting out withDisableAuthCheckskips the check, otherwise the gate still runs. It lives incmdutilbeside the helper, since the coupling is the helper's, not any one command's.Notes for reviewers
Three atomic commits:
fix(release): don't let a failed draft lookup mask a found releasechanges the selector inFetchReleaseand adds a regression case to the existingTest_downloadRuntable.feat(release): allow download without authenticationremoves the gate in the download command.fix(cmdutil): honor DisableAuthCheck under repo-override parentsfixes the repo-override helper so the gate removal actually takes effect, and adds an integration test that pins the coupling between repo override and the root auth gate.Additional Context
gh release downloadon a public repo.gh extension install#13176 removed the same gate forgh extension installand is the precedent this follows.