{"record":{"id":"8282055c41923c05","repo":"JuliusBrussee/caveman","slug":"compat-upstream-q-base-url-is-required","errorCode":null,"errorMessage":"compat upstream %q: base_url is required","messagePattern":"compat upstream %q: base_url is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/config/config.go","lineNumber":248,"sourceCode":"}\n\n// BedrockBaseURL returns an explicit operator override or derives AWS's standard\n// Runtime endpoint from BedrockRegion. Callers never need to paste a raw URL for\n// the normal first-party path.\nfunc (c Config) BedrockBaseURL() string {\n\tif configured := c.BaseURL(\"bedrock\", \"\"); configured != \"\" {\n\t\treturn configured\n\t}\n\treturn fmt.Sprintf(\"https://bedrock-runtime.%s.amazonaws.com\", c.BedrockRegion())\n}\n\nfunc (c Config) validateCompat() error {\n\tfor name, upstream := range c.Compat {\n\t\tif err := openaicompat.ValidateName(name); err != nil {\n\t\t\treturn fmt.Errorf(\"compat upstream %q: %w\", name, err)\n\t\t}\n\t\tif strings.TrimSpace(upstream.BaseURL) == \"\" {\n\t\t\treturn fmt.Errorf(\"compat upstream %q: base_url is required\", name)\n\t\t}\n\t\tif err := openaicompat.ValidateBaseURL(upstream.BaseURL); err != nil {\n\t\t\treturn fmt.Errorf(\"compat upstream %q: base_url: %w\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// providerEnvKey maps a provider name to the BYOK environment variable that\n// holds its API key.\nvar providerEnvKey = map[string]string{\n\t\"anthropic\":         \"ANTHROPIC_API_KEY\",\n\t\"openai\":            \"OPENAI_API_KEY\",\n\t\"gemini\":            \"GEMINI_API_KEY\",\n\t\"azure_openai\":      \"AZURE_OPENAI_API_KEY\",\n\t\"openai_compatible\": \"OPENAI_COMPAT_API_KEY\",\n}\n","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/config/config.go#L230-L266","documentation":"validateCompat requires every compat upstream entry to carry a non-blank base_url. Named compat mounts are independent static upstreams, so unlike first-party providers there is no default URL to fall back on — an empty or whitespace-only base_url cannot be routed and fails config load.","triggerScenarios":"compat entry with base_url omitted, set to \"\", or set to \"   \" (whitespace) in caveman.yaml; or the corresponding env/structured config field never populated when building Config programmatically.","commonSituations":"Copy-pasting a compat example block but deleting the URL; intending to set the URL later via env and forgetting; YAML indentation putting base_url under the wrong key so it is never read.","solutions":["Add the provider's API base URL, e.g. base_url: https://openrouter.ai/api/v1","Verify YAML indentation so base_url sits under the correct compat name","Remove the compat entry entirely if the upstream is not actually used"],"exampleFix":"# before\ncompat:\n  openrouter: {}      # Error[1063]: base_url is required\n\n# after\ncompat:\n  openrouter:\n    base_url: https://openrouter.ai/api/v1","handlingStrategy":"validation","validationCode":"for name, up := range cfg.Compat {\n    if strings.TrimSpace(up.BaseURL) == \"\" {\n        return fmt.Errorf(\"compat %q: base_url missing — add it before launch\", name)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat compat entries as incomplete without base_url — there is no default URL","Schema-validate caveman.yaml (required fields per compat entry) before deploy","Delete unused compat entries instead of leaving stubs"],"tags":["config","compat","validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}