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

  1. Verify the ref exists: `gh api repos/OWNER/REPO/git/ref/heads/REF` and `.../git/ref/tags/REF`
  2. Fix typos or casing in the ref string; branch and tag names are case-sensitive
  3. If the ref lives on a fork or another repo, point the owner/repo arguments at that repository
  4. 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

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


AI-assisted analysis of cli/cli@0eeec0b92e (2026-08-15). Data as JSON: /api/errors/d70e92fde103abda. Report an issue: GitHub.