JuliusBrussee/caveman · error
provider %q has no configured upstream URL
Error message
provider %q has no configured upstream URL
What it means
Base.ResolveUpstreamURL determines where to forward a request: it uses route.BaseURL if set, else the adapter's configured BaseURL, and refuses to proceed when the result is empty/whitespace. Without a base URL the proxy has no upstream to dial, so it fails closed rather than guessing a provider default.
Source
Thrown at proxy/providers/adapter.go:408
for _, route := range b.Routes {
// Routes ending in '/' are explicit subtree mounts (for example
// /compat/ and /azure/). Every other route is exact. Treating all routes
// as prefixes made /v1/responses-anything and /v1/messages/unknown valid
// proxy surfaces, violating the gateway's closed route allowlist.
if path == route || strings.HasSuffix(route, "/") && strings.HasPrefix(path, route) {
return true
}
}
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, nilView on GitHub (pinned to 27d5a3981a)
Solutions
- Set the provider's base URL in config (e.g. base_url: https://api.openai.com) or the corresponding env var, then restart.
- If overriding per route, confirm the route actually populates BaseURL — empty string falls back to the adapter config, which must then be non-empty.
- Print the effective config at startup or add a config lint that fails when a enabled provider lacks a base URL.
Example fix
# before (caveman.yaml)
providers:
openai-compat:
api_key: sk-...
# base_url missing -> error on first request
# after
providers:
openai-compat:
api_key: sk-...
base_url: https://api.mylab.local/v1 Defensive patterns
Strategy: validation
Validate before calling
func hasUpstreamBaseURL(routeBase, adapterBase string) bool {
effective := routeBase
if strings.TrimSpace(effective) == "" {
effective = adapterBase
}
return strings.TrimSpace(effective) != ""
}
if !hasUpstreamBaseURL(route.BaseURL, adapter.BaseURL) {
return fmt.Errorf("provider %s: configure base_url before routing traffic", name)
} Try / catch
if _, err := adapter.ResolveUpstreamURL(ctx, req, route); err != nil {
if strings.Contains(err.Error(), "has no configured upstream URL") {
http.Error(w, "provider not configured with an upstream base URL", http.StatusBadGateway)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
} Prevention
- Fail config load when an enabled provider lacks base_url.
- Print the effective (route override or adapter) base URL at startup.
- Watch YAML indentation so base_url lands under the right provider.
When it happens
Trigger: A provider entry (e.g. in caveman.yaml or the managed route config) was created with credentials but no base_url; RouteContext carried an empty BaseURL and the adapter default was never set; the env var backing the adapter's base URL was unset in the deployment unit.
Common situations: Adding a new openai-compatible provider and forgetting the base_url field; base URL configured only for a different environment/profile; YAML indentation puts base_url under the wrong provider block so it parses as absent.
Related errors
- provider %q upstream URL must be absolute
- bedrock configured endpoint kind %q does not allow request k
- cachebench: no providers
- cachebench: provider population exceeds 1024
- cachebench: invalid observation read limits
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/292befc3ef0991a9.
Report an issue: GitHub.