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
- Set BaseURL for the provider in CAVE_COMPAT_UPSTREAMS / compat: config (e.g. https://api.example.com/v1)
- Check the exact env var name and that it is exported in the environment
- Restart the process after updating config so the new value is loaded
- 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
- Validate all upstream configs at startup, not at request time
- Fail fast with a clear message when env vars are empty
- Use defaults or templated config so URL fields are never silently blank
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
- compat upstream name
- compat upstream name
- compat upstream base_url
- compat upstream forward_headers
- compat upstream
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)