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 cannot

View on GitHub (pinned to 0eeec0b92e)

Solutions

  1. Pass a bare "OWNER/REPO" string, e.g. "cli/cli"; strip any host prefix or trailing slash before calling
  2. If the input may carry a host, parse it first with ghrepo.FromFullName and rebuild owner/name from the resulting Repository fields
  3. 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

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


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