JuliusBrussee/caveman · error

provider has no configured upstream URL

Error message

provider %q has no configured upstream URL

What it means

parseBaseURL requires a non-empty BaseURL string for the named compat provider before it can compute the upstream target. An empty or whitespace-only value means the provider was registered without an upstream URL, so requests routed to it cannot be forwarded.

Solutions

  1. Set BaseURL for the provider in CAVE_COMPAT_UPSTREAMS / compat: config (e.g. https://api.example.com/v1)
  2. Check the exact env var name and that it is exported in the environment
  3. Restart the process after updating config so the new value is loaded
  4. Remove the provider entry if it is not meant to be routed

Example fix

// before
CAVE_COMPAT_UPSTREAMS=myprovider:  # no URL

// after
CAVE_COMPAT_UPSTREAMS=myprovider:https://api.myprovider.com/v1
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(cfg.BaseURL) == "" {
    return fmt.Errorf("provider %q requires a non-empty upstream URL", name)
}

Try / catch

if err := ValidateBaseURL(providerCfg.BaseURL); err != nil {
    return fmt.Errorf("startup config invalid: %w", err)
}

Prevention

When it happens

Trigger: A named compat upstream defined in compat: (standalone) or CAVE_COMPAT_UPSTREAMS (managed) with an empty BaseURL; env var entry present but the URL field blank; config parsed with missing keys defaulting to ""; calling ResolveUpstreamURL or ValidateBaseURL on a zero-value adapter.

Common situations: Typos in env var names so the URL field stays empty; partially written config committed before the URL was filled in; secrets managers returning empty strings for missing values.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/13e79d73b99b62b5. Report an issue: GitHub.

Appendix: source

Thrown at proxy/providers/openaicompat/openaicompat.go:513

		}
		if segment == "." || segment == ".." {
			return fmt.Errorf("dot segments are not allowed in path")
		}
	}
	// URL.Path is decoded by net/url while RawPath retains a valid escaped
	// spelling. Reject separators, backslashes, and dot bytes in either form so
	// a path cannot change route identity after another decoder or proxy hop.
	for _, escape := range []string{"%2f", "%5c", "%2e"} {
		if strings.Contains(strings.ToLower(path), escape) || strings.Contains(strings.ToLower(rawPath), escape) {
			return fmt.Errorf("ambiguous escaped path sequence %s", escape)
		}
	}
	return nil
}

func parseBaseURL(raw, provider string) (*url.URL, error) {
	if strings.TrimSpace(raw) == "" {
		return nil, fmt.Errorf("provider %q has no configured upstream URL", provider)
	}
	u, err := url.Parse(strings.TrimSpace(raw))
	if err != nil {
		return nil, err
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return nil, fmt.Errorf("provider %q upstream URL scheme %q is not allowed", provider, u.Scheme)
	}
	if !u.IsAbs() || u.Host == "" || u.Hostname() == "" {
		return nil, fmt.Errorf("provider %q upstream URL must be an absolute URL with a host", provider)
	}
	if u.User != nil {
		return nil, fmt.Errorf("provider %q upstream URL must not include userinfo", provider)
	}
	if u.Fragment != "" {
		return nil, fmt.Errorf("provider %q upstream URL must not include a fragment", provider)
	}
	if err := validatePathComponents(u.Path, u.RawPath); err != nil {

View on GitHub (pinned to 3ee70a1026)