cli/cli · error
expected the \"OWNER/REPO\" format, got %q
Error message
expected the \"OWNER/REPO\" format, got %q
What it means
RepoPartsFromNWO in internal/safeurl parses a raw "OWNER/REPO" string and throws this error when the input does not contain exactly one slash with a non-empty owner and a non-empty repo name. It is deliberately stricter than ghrepo.FromFullName: it rejects the "[HOST/]OWNER/REPO" form and any value with extra slashes, so a host component or smuggled extra path segments cannot pass through as owner or repo. Any caller that feeds it a full name like "github.com/cli/cli" or "cli" will fail.
Source
Thrown at internal/safeurl/safeurl.go:24
import (
"fmt"
"net/url"
"strings"
)
// RepoPartsFromNWO parses a raw "owner/repo" string and returns the owner and name
// unescaped. It returns an error unless nwo contains exactly one slash with a non-empty
// owner and name, so a value carrying extra slashes cannot smuggle additional path
// segments through as the owner or name.
//
// This intentionally does not reuse ghrepo.FromFullName, which accepts the broader
// "[HOST/]OWNER/REPO" form. The call sites here only ever handle a bare "OWNER/REPO",
// so a stricter parse that rejects an unexpected host component is the safer fit.
func RepoPartsFromNWO(nwo string) (owner, name string, err error) {
parts := strings.Split(nwo, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", fmt.Errorf("expected the \"OWNER/REPO\" format, got %q", nwo)
}
return parts[0], parts[1], nil
}
// SafeURL is the sealed interface implemented by the URL types in this package.
// It exists so that a value known to address a safe REST API URL can be passed
// around and rendered without exposing how it was built.
type SafeURL interface {
String() string
// The sealed method keeps the set of implementations closed to this package,
// so callers outside it cannot forge a value that claims to be safe.
sealed()
}
// MutableSafeURL is a REST API URL built from a host prefix, path components, and query
// parameters. The path components and query parameters are URL encoded (aka
// percent-encoded) when the URL is rendered so that caller supplied values cannotView on GitHub (pinned to 0eeec0b92e)
Solutions
- Pass a bare "OWNER/REPO" string, e.g. "cli/cli"; strip any host prefix or trailing slash before calling
- If the input may carry a host, parse it first with ghrepo.FromFullName and rebuild owner/name from the resulting Repository fields
- Trim slashes and split input yourself, verifying len(parts)==2 and both parts non-empty, before handing it to this function
Example fix
// before
owner, repo, err := safeurl.RepoPartsFromNWO("github.com/cli/cli")
// after
r, err := ghrepo.FromFullName("github.com/cli/cli") // tolerates HOST/OWNER/REPO
if err != nil {
return err
}
owner, repo, err := safeurl.RepoPartsFromNWO(r.RepoSlug()) // now bare OWNER/REPO Defensive patterns
Strategy: validation
Validate before calling
// ValidateNWO reports whether s is a bare "OWNER/REPO" with non-empty parts.
func ValidateNWO(s string) bool {
parts := strings.Split(s, "/")
return len(parts) == 2 && parts[0] != "" && parts[1] != ""
}
if !ValidateNWO(fullName) {
return fmt.Errorf("%q must be OWNER/REPO without host", fullName)
}
owner, repo, err := safeurl.RepoPartsFromNWO(fullName) Prevention
- Normalize names through ghrepo.FromFullName and pass r.RepoSlug() so no host component ever reaches the parser
- Trim leading/trailing slashes from user input before parsing
- Reject inputs containing more than one slash at the CLI boundary with a usage message
When it happens
Trigger: Calling safeurl.RepoPartsFromNWO with: a bare repo name ("cli"), a full name with host ("github.com/cli/cli"), a trailing/leading slash ("cli/" or "/cli"), an empty string, or a value with two or more slashes ("a/b/c"). Only exact "owner/repo" with both parts non-empty passes.
Common situations: Passing a value obtained from ghrepo.FromFullName().RepoSlug() mixed with host-qualified names, reusing user input that may include a hostname, or forgetting to strip a trailing slash from a repo argument in a CLI wrapper around skills discovery.
Related errors
- username cannot be blank
- agent tasks are not supported on this host
- task description file cannot be empty
- remote tracking branch must have format refs/remotes/<remote
- path component %q would traverse the URL path
AI-assisted analysis of cli/cli@0eeec0b92e (2026-08-15).
Data as JSON: /api/errors/ba0091731580e798.
Report an issue: GitHub.