Skip to content

browse: disambiguate decimal-only short SHAs from issue numbers#13347

Open
SebTardif wants to merge 2 commits into
cli:trunkfrom
SebTardif:fix/browse-disambiguate-decimal-sha
Open

browse: disambiguate decimal-only short SHAs from issue numbers#13347
SebTardif wants to merge 2 commits into
cli:trunkfrom
SebTardif:fix/browse-disambiguate-decimal-sha

Conversation

@SebTardif
Copy link
Copy Markdown

Fixes #12357

Summary

When a selector argument passed to gh browse consists entirely of decimal digits and is at least 7 characters long (e.g. 309628980), it satisfies both isNumber and isCommit. Previously isNumber was always checked first, so gh browse 309628980 would open an issues URL even when that string referred to an actual commit SHA.

This PR disambiguates only in the ambiguous case by checking the commits API:

  • If the selector has no # prefix and satisfies both isNumber and isCommit, call HEAD /repos/{owner}/{repo}/commits/{ref}
  • If the commit exists (HTTP 200), open the commit URL
  • If the commit does not exist (HTTP 404/422), fall back to the issues URL
  • The # prefix still forces issue behavior
  • No API call is made for unambiguous inputs (non-decimal hex chars, short numbers under 7 digits, or #-prefixed args)

Acceptance Criteria Coverage

AC1: Ambiguous decimal-only short hash that IS a valid commit
API returns 200 → parseSection returns commit/309628980. Covered by test case "decimal-only short SHA that is a valid commit".

AC2: Ambiguous decimal-only string that is NOT a valid commit
API returns 404 → parseSection falls through to issues/1234567. Covered by test case "decimal-only string that is not a valid commit".

AC3: # prefix forces issue
The strings.HasPrefix(opts.SelectorArg, "#") check short-circuits before any API call. Covered by test case "decimal-only short SHA with # prefix forces issue".

Changes

File Change
api/queries_repo.go Add CommitExists helper (mirrors existing RepoExists pattern)
pkg/cmd/browse/browse.go Disambiguate when selector is both numeric and commit-like
pkg/cmd/browse/browse_test.go Three new test cases covering all acceptance criteria

Validation

$ go test ./pkg/cmd/browse/... ./api/...
ok   github.com/cli/cli/v2/pkg/cmd/browse
ok   github.com/cli/cli/v2/api

$ go vet ./pkg/cmd/browse/... ./api/...
(clean)

$ go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.0 run ./pkg/cmd/browse/... ./api/...
0 issues.

When a selector argument passed to gh browse consists entirely of
decimal digits and is at least 7 characters long, it satisfies both
isNumber and isCommit. Previously isNumber was always checked first,
so gh browse 3096289 would open an issues URL even when that string
referred to an actual commit SHA.

Only in the ambiguous case (decimal-only, no # prefix, passes both
checks), call the commits API to determine whether the ref resolves to
a real commit. If it does, open the commit URL; otherwise fall back to
the issues URL. No API call is made for unambiguous inputs.

Fixes cli#12357
@SebTardif SebTardif requested a review from a team as a code owner May 4, 2026 18:20
@SebTardif SebTardif requested review from babakks and Copilot May 4, 2026 18:20
@github-actions github-actions Bot added external pull request originating outside of the CLI core team needs-triage needs to be reviewed labels May 4, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes gh browse so ambiguous decimal-only selectors that also look like short commit SHAs are checked against the commits API before deciding between an issue URL and a commit URL.

Changes:

  • Added commit-existence lookup logic for ambiguous numeric selectors in gh browse.
  • Added an API helper to probe whether a commit ref exists.
  • Added browse command tests for valid commit, invalid commit, and #-prefixed issue cases.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
pkg/cmd/browse/browse.go Adds the disambiguation branch that probes the commits API before choosing issues/ vs commit/.
pkg/cmd/browse/browse_test.go Adds end-to-end command tests for the new ambiguous-selector behavior.
api/queries_repo.go Adds CommitExists to check commit existence via a HEAD request.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/cmd/browse/browse.go
Comment on lines +255 to +267
if !strings.HasPrefix(opts.SelectorArg, "#") && isCommit(opts.SelectorArg) {
httpClient, err := opts.HttpClient()
if err != nil {
return "", err
}
apiClient := api.NewClientFromHTTP(httpClient)
exists, err := api.CommitExists(apiClient, baseRepo, opts.SelectorArg)
if err != nil {
return "", err
}
if exists {
return fmt.Sprintf("commit/%s", opts.SelectorArg), nil
}
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. The maintainer explicitly chose correctness over graceful degradation in the issue discussion:

I think it's rare enough that we should prioritise correctness here and hit the API.

The issue author agreed. Silently guessing the wrong URL during an outage would be a worse user experience than surfacing the error — especially since gh browse already makes API calls on the common path (e.g. to resolve the default branch).

Comment thread api/queries_repo.go
Comment on lines +1688 to +1704
func CommitExists(client *Client, repo ghrepo.Interface, ref string) (bool, error) {
path := fmt.Sprintf("repos/%s/%s/commits/%s", repo.RepoOwner(), repo.RepoName(), ref)

resp, err := client.HTTP().Head(ghinstance.RESTPrefix(repo.RepoHost()) + path)
if err != nil {
return false, err
}

defer resp.Body.Close()

switch resp.StatusCode {
case 200:
return true, nil
case 404, 422:
return false, nil
default:
return false, ghAPI.HandleHTTPError(resp)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — added direct unit tests for CommitExists in api/queries_repo_test.go (second commit). Covers all four status code paths: 200 (exists), 404 (not found), 422 (unprocessable ref), and 500 (server error).

Cover all status code paths: 200 (exists), 404 (not found),
422 (unprocessable ref), and 500 (server error).
@SebTardif
Copy link
Copy Markdown
Author

@williammartin This PR implements the fix per your acceptance criteria. All three scenarios are covered with tests. Happy to adjust anything based on your feedback.

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

Labels

external pull request originating outside of the CLI core team needs-triage needs to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gh browse mistakes short hash for issue/pr number

2 participants