cli/cli · error
ref %q not found as branch, tag, or commit in %s/%s
Error message
ref %q not found as branch, tag, or commit in %s/%s
What it means
resolveExplicitRef in internal/skills/discovery tries a short version string first as a branch, then as a tag, and finally as a commit SHA (GET repos/{owner}/{repo}/commits/{ref}). If all three lookups return 404 (and only 404; other HTTP errors propagate earlier), it throws this aggregate "not found as branch, tag, or commit" error. It means the ref string simply does not exist in the repository in any of the three forms.
Source
Thrown at internal/skills/discovery/discovery.go:271
return resolved, nil
} else if !isNotFound(err) {
return nil, err
}
commitPath, err := safeurl.JoinPath("repos", owner, repo, "commits", ref)
if err != nil {
return nil, err
}
var commitResp struct {
SHA string `json:"sha"`
}
if err := client.REST(host, "GET", commitPath.String(), nil, &commitResp); err == nil {
return &ResolvedRef{Ref: commitResp.SHA, SHA: commitResp.SHA}, nil
} else if !isNotFound(err) {
return nil, err
}
return nil, fmt.Errorf("ref %q not found as branch, tag, or commit in %s/%s", ref, owner, repo)
}
// resolveTagRef looks up a tag by short name and returns a fully qualified ref.
// For annotated tags, the tag object is dereferenced to obtain the commit SHA.
func resolveTagRef(client *api.Client, host, owner, repo, tag string) (*ResolvedRef, error) {
tagPath, err := safeurl.JoinPath("repos", owner, repo, "git", "ref", fmt.Sprintf("tags/%s", tag))
if err != nil {
return nil, err
}
var refResp struct {
Object struct {
SHA string `json:"sha"`
Type string `json:"type"`
} `json:"object"`
}
if err := client.REST(host, "GET", tagPath.String(), nil, &refResp); err != nil {
return nil, fmt.Errorf("tag %q not found in %s/%s: %w", tag, owner, repo, err)
}View on GitHub (pinned to 0eeec0b92e)
Solutions
- Verify the ref exists: `gh api repos/OWNER/REPO/git/ref/heads/REF` and `.../git/ref/tags/REF`
- Fix typos or casing in the ref string; branch and tag names are case-sensitive
- If the ref lives on a fork or another repo, point the owner/repo arguments at that repository
- Use a full 40-char SHA or a longer unique prefix when passing commits
Example fix
// before
resolved, err := discovery.ResolveRef(client, host, owner, repo, "v1.2.3")
// after
resolved, err := discovery.ResolveRef(client, host, owner, repo, "v1.2.3")
if err != nil {
// check spelling against the repo's actual refs before failing
_ = runGH("api", fmt.Sprintf("repos/%s/%s/git/matching-refs/", owner, repo))
} Defensive patterns
Strategy: validation
Validate before calling
// ValidateRefShape cheap-checks a ref string before any API call.
func ValidateRefShape(ref string) error {
if ref == "" || ref == "latest" {
return nil // handled specially
}
if strings.ContainsAny(ref, " ~^:[?*\\") {
return fmt.Errorf("ref %q contains invalid characters", ref)
}
return nil
}
if err := ValidateRefShape(version); err != nil {
return err
} Try / catch
resolved, err := discovery.ResolveRef(client, host, owner, repo, version)
if err != nil {
var httpErr *api.HTTPError
if errors.As(err, &httpErr) || strings.Contains(err.Error(), "not found as branch, tag, or commit") {
// ref genuinely absent: surface actionable hint, do not retry
return fmt.Errorf("ref %q not found in %s/%s; check spelling with `gh api repos/%s/%s/git/matching-refs/`", version, owner, repo, owner, repo)
}
return err // transport failure: safe to retry
} Prevention
- Verify ref existence once with the matching-refs endpoint before entering a discovery flow
- Prefer fully qualified refs (refs/tags/v1.2.3) to skip the ambiguous branch/tag/commit chain
- Remember short-name resolution tries branch first: a same-named branch shadows the tag
When it happens
Trigger: ResolveRef called with a version that is not a branch, not a tag, and not resolvable as a commit: a typo ("main" vs "main"), a tag that was deleted, a branch on a fork rather than the target repo, or a short SHA too ambiguous for the commits endpoint. The calls made are GET .../git/ref/heads/{ref}, GET .../git/ref/tags/{ref}, then GET .../commits/{ref}, each returning 404.
Common situations: User passes --ref vX.Y.Z when the tag is spelled v_X_Y_Z or the release exists only on another repo; the default branch was renamed; the commit SHA was truncated to fewer characters than the API tolerates; the repo is a fork and the ref lives upstream.
Related errors
- tag %q not found in %s/%s: %w
- branch %q not found in %s/%s: %w
- path %q not found in %s/%s: %w
- could not dereference annotated tag %q: %w
- could not fetch latest release: %w
AI-assisted analysis of cli/cli@0eeec0b92e (2026-08-15).
Data as JSON: /api/errors/d70e92fde103abda.
Report an issue: GitHub.