JuliusBrussee/caveman · error
provider %q upstream URL must be absolute
Error message
provider %q upstream URL must be absolute
What it means
After establishing a non-empty base URL, Base.ResolveUpstreamURL parses it and requires the result to be absolute with a hostname. A value like 'api.openai.com' (no scheme), '/v1' (relative), or 'file:///path' (no host) fails this check. The proxy joins request paths onto this URL, so a relative base would produce an un-dialable target.
Source
Thrown at proxy/providers/adapter.go:415
}
}
return false
}
func (b Base) ResolveUpstreamURL(ctx context.Context, req *http.Request, route RouteContext) (*url.URL, error) {
baseURL := b.BaseURL
if route.BaseURL != "" {
baseURL = route.BaseURL
}
if strings.TrimSpace(baseURL) == "" {
return nil, fmt.Errorf("provider %q has no configured upstream URL", b.Provider)
}
base, err := url.Parse(baseURL)
if err != nil {
return nil, err
}
if !base.IsAbs() || base.Hostname() == "" {
return nil, fmt.Errorf("provider %q upstream URL must be absolute", b.Provider)
}
path := req.URL.Path
for _, prefix := range []string{"/openai", "/anthropic", "/gemini", "/compat/stub", "/compat/openai-compatible"} {
path = strings.TrimPrefix(path, prefix)
}
if strings.HasPrefix(req.URL.Path, "/azure/") {
path = strings.TrimPrefix(req.URL.Path, "/azure")
}
base.Path = strings.TrimRight(base.Path, "/") + path
base.RawQuery = req.URL.RawQuery
return base, nil
}
func (b Base) SanitizeAndMapHeaders(ctx context.Context, req *http.Request, credential Credential, _ *url.URL) (http.Header, error) {
out := http.Header{}
copyIfPresent(out, req.Header, "content-type")
copyIfPresent(out, req.Header, "accept")
copyIfPresent(out, req.Header, "accept-encoding")View on GitHub (pinned to 27d5a3981a)
Solutions
- Write the full absolute URL including scheme and host: base_url: https://api.openai.com.
- Validate config at load: u, err := url.Parse(v); err == nil && u.IsAbs() && u.Hostname() != "" — reject early with the provider name.
- Check for shell/YAML mangling (quotes, comments) that truncates the value to a relative fragment.
Example fix
# before base_url: api.openai.com # after base_url: https://api.openai.com
Defensive patterns
Strategy: validation
Validate before calling
func isAbsoluteHTTPURL(raw string) bool {
u, err := url.Parse(strings.TrimSpace(raw))
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Hostname() != ""
}
if !isAbsoluteHTTPURL(baseURL) {
return fmt.Errorf("base_url %q must be an absolute http(s) URL", baseURL)
} Type guard
func isAbsoluteHTTPURL(raw string) bool {
u, err := url.Parse(strings.TrimSpace(raw))
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Hostname() != ""
} Try / catch
if _, err := adapter.ResolveUpstreamURL(ctx, req, route); err != nil {
if strings.Contains(err.Error(), "must be absolute") {
http.Error(w, "provider base URL must include scheme and host", http.StatusBadGateway)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
} Prevention
- Always write base URLs with scheme://host.
- Lint config: url.Parse + IsAbs + Hostname checks at load time.
- Beware shell/env truncation that strips the scheme.
When it happens
Trigger: Configuring base_url: api.openai.com/v1 without https://; using a leading-slash relative path; a base URL of just 'http://' with the host in a different field; copy-paste dropping the scheme.
Common situations: Operators trimming 'the obvious prefix' from docs examples; docker env values losing the scheme to shell parsing; mixing up host-only fields and full-URL fields when porting config from another tool.
Related errors
- provider %q has no configured upstream URL
- bedrock base url invalid: %w
- bedrock configured endpoint kind %q does not allow request k
- cachebench: no providers
- cachebench: provider population exceeds 1024
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/841c8e40fbe78c77.
Report an issue: GitHub.