{"record":{"id":"292befc3ef0991a9","repo":"JuliusBrussee/caveman","slug":"provider-q-has-no-configured-upstream-url","errorCode":null,"errorMessage":"provider %q has no configured upstream URL","messagePattern":"provider %q has no configured upstream URL","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/adapter.go","lineNumber":408,"sourceCode":"\tfor _, route := range b.Routes {\n\t\t// Routes ending in '/' are explicit subtree mounts (for example\n\t\t// /compat/ and /azure/). Every other route is exact. Treating all routes\n\t\t// as prefixes made /v1/responses-anything and /v1/messages/unknown valid\n\t\t// proxy surfaces, violating the gateway's closed route allowlist.\n\t\tif path == route || strings.HasSuffix(route, \"/\") && strings.HasPrefix(path, route) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (b Base) ResolveUpstreamURL(ctx context.Context, req *http.Request, route RouteContext) (*url.URL, error) {\n\tbaseURL := b.BaseURL\n\tif route.BaseURL != \"\" {\n\t\tbaseURL = route.BaseURL\n\t}\n\tif strings.TrimSpace(baseURL) == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q has no configured upstream URL\", b.Provider)\n\t}\n\tbase, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !base.IsAbs() || base.Hostname() == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL must be absolute\", b.Provider)\n\t}\n\tpath := req.URL.Path\n\tfor _, prefix := range []string{\"/openai\", \"/anthropic\", \"/gemini\", \"/compat/stub\", \"/compat/openai-compatible\"} {\n\t\tpath = strings.TrimPrefix(path, prefix)\n\t}\n\tif strings.HasPrefix(req.URL.Path, \"/azure/\") {\n\t\tpath = strings.TrimPrefix(req.URL.Path, \"/azure\")\n\t}\n\tbase.Path = strings.TrimRight(base.Path, \"/\") + path\n\tbase.RawQuery = req.URL.RawQuery\n\treturn base, nil","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/providers/adapter.go#L390-L426","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before (caveman.yaml)\nproviders:\n  openai-compat:\n    api_key: sk-...\n    # base_url missing -> error on first request\n\n# after\nproviders:\n  openai-compat:\n    api_key: sk-...\n    base_url: https://api.mylab.local/v1","handlingStrategy":"validation","validationCode":"func hasUpstreamBaseURL(routeBase, adapterBase string) bool {\n    effective := routeBase\n    if strings.TrimSpace(effective) == \"\" {\n        effective = adapterBase\n    }\n    return strings.TrimSpace(effective) != \"\"\n}\n\nif !hasUpstreamBaseURL(route.BaseURL, adapter.BaseURL) {\n    return fmt.Errorf(\"provider %s: configure base_url before routing traffic\", name)\n}","typeGuard":null,"tryCatchPattern":"if _, err := adapter.ResolveUpstreamURL(ctx, req, route); err != nil {\n    if strings.Contains(err.Error(), \"has no configured upstream URL\") {\n        http.Error(w, \"provider not configured with an upstream base URL\", http.StatusBadGateway)\n        return\n    }\n    http.Error(w, err.Error(), http.StatusBadRequest)\n}","preventionTips":["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."],"tags":["go","configuration","proxy","routing"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}