tailscale/tailscale · error
URL %q is missing scheme or host
Error message
URL %q is missing scheme or host
What it means
Returned by distsign.DownloadVerified when srcURL parsed successfully but has an empty Scheme or Host component. DownloadVerified needs a full absolute URL because it splits it into a scheme://host base for constructing the verified-download Client; a host-only or path-only string cannot be split that way.
Source
Thrown at clientupdate/distsign/url.go:30
"tailscale.com/types/logger"
)
// DownloadVerified is a convenience wrapper around [Client.Download]
// for callers that have a full URL (e.g.
// https://pkgs.tailscale.com/unstable/foo.gaf) rather than a base URL
// plus path. It splits srcURL into a base ("scheme://host") and a path,
// constructs a [Client] for the base, and downloads with signature
// verification to dstPath.
func DownloadVerified(ctx context.Context, logf logger.Logf, srcURL, dstPath string) error {
if logf == nil {
logf = logger.Discard
}
u, err := url.Parse(srcURL)
if err != nil {
return fmt.Errorf("parsing URL %q: %w", srcURL, err)
}
if u.Scheme == "" || u.Host == "" {
return fmt.Errorf("URL %q is missing scheme or host", srcURL)
}
base := &url.URL{Scheme: u.Scheme, User: u.User, Host: u.Host}
path := strings.TrimPrefix(u.Path, "/")
if path == "" {
return fmt.Errorf("URL %q has no path component", srcURL)
}
c, err := NewClient(logf, base.String())
if err != nil {
return err
}
return c.Download(ctx, path, dstPath)
}
View on GitHub (pinned to cfe32b8be6)
Solutions
- Pass a fully-qualified URL including scheme, e.g. https://pkgs.tailscale.com/unstable/foo.gaf
- Normalize at config load: if the stored value lacks "://", prepend "https://"
- Pre-validate with u, _ := url.Parse(srcURL); reject when u.Scheme == "" || u.Host == ""
Example fix
// before err := distsign.DownloadVerified(ctx, logf, "pkgs.tailscale.com/tailscale.tgz", dst) // after err := distsign.DownloadVerified(ctx, logf, "https://pkgs.tailscale.com/tailscale.tgz", dst)
Defensive patterns
Strategy: validation
Validate before calling
func requireAbsoluteURL(raw string) (string, error) {
raw = strings.TrimSpace(raw)
if !strings.Contains(raw, "://") {
raw = "https://" + raw // or reject, per policy
}
u, err := url.Parse(raw)
if err != nil {
return "", err
}
if u.Scheme == "" || u.Host == "" {
return "", fmt.Errorf("URL %q is missing scheme or host", raw)
}
return u.String(), nil
} Type guard
func hasSchemeAndHost(s string) bool {
u, err := url.Parse(strings.TrimSpace(s))
return err == nil && u.Scheme != "" && u.Host != ""
} Prevention
- Store full absolute URLs (scheme + host + path) in configuration rather than bare hostnames
- Normalize scheme-less values to https:// at load time and log the normalization
- Prefer building request URLs with url.URL fields instead of string concatenation
When it happens
Trigger: Calling DownloadVerified with "pkgs.tailscale.com/tailscale_1.2.3_amd64.tgz" (no scheme), "https:///unstable/foo.gaf" (no host), or a URL assembled by concatenating a bare hostname with a path.
Common situations: Configuration that stores a hostname instead of a full URL; code that builds the URL by string concatenation and forgets the scheme; switching from a Client+relative-path API to the DownloadVerified convenience wrapper without adding the scheme.
Related errors
- parsing URL %q: %w
- URL %q has no path component
- invalid ServerURL scheme, must be http or https
- invalid pkgsAddr %q: %w
- creating request: %w
AI-assisted analysis of tailscale/tailscale@cfe32b8be6 (2026-08-15).
Data as JSON: /api/errors/c8df7637569df1c1.
Report an issue: GitHub.