{"record":{"id":"28aee1b53029f625","repo":"JuliusBrussee/caveman","slug":"provider-q-upstream-url-must-not-include-a-fragme","errorCode":null,"errorMessage":"provider %q upstream URL must not include a fragment","messagePattern":"provider %q upstream URL must not include a fragment","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":529,"sourceCode":"func parseBaseURL(raw, provider string) (*url.URL, error) {\n\tif strings.TrimSpace(raw) == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q has no configured upstream URL\", provider)\n\t}\n\tu, err := url.Parse(strings.TrimSpace(raw))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif u.Scheme != \"http\" && u.Scheme != \"https\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL scheme %q is not allowed\", provider, u.Scheme)\n\t}\n\tif !u.IsAbs() || u.Host == \"\" || u.Hostname() == \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL must be an absolute URL with a host\", provider)\n\t}\n\tif u.User != nil {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL must not include userinfo\", provider)\n\t}\n\tif u.Fragment != \"\" {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL must not include a fragment\", provider)\n\t}\n\tif err := validatePathComponents(u.Path, u.RawPath); err != nil {\n\t\treturn nil, fmt.Errorf(\"provider %q upstream URL path rejected: %w\", provider, err)\n\t}\n\treturn u, nil\n}\n","sourceCodeStart":511,"sourceCodeEnd":536,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L511-L536","documentation":"parseBaseURL rejects a base URL that contains a '#fragment' component (openaicompat.go:528). A fragment is a client-side-only locator; it is meaningless on an HTTP request line and would either be dropped or corrupt the joined upstream URL, so the adapter fails closed instead of ignoring it silently.","triggerScenarios":"Configure a compat provider base URL like 'https://api.example.com/v1#production' or paste a URL copied from a browser address bar that includes an anchor, then call ResolveUpstreamURL or ValidateBaseURL.","commonSituations":"Pasting URLs directly from browser tabs (anchors like #models or #settings included), or documentation links where the anchor was part of the copy.","solutions":["Delete everything from the '#' character onward in the configured base URL.","Verify the remaining value is 'scheme://host[:port]/path' and re-run ValidateBaseURL.","If the fragment was meant to select a route/section, encode that intent in the path or a config option instead."],"exampleFix":"// before\nbase_url: https://api.example.com/v1#chat\n// after\nbase_url: https://api.example.com/v1","handlingStrategy":"validation","validationCode":"if u, err := url.Parse(raw); err == nil && u.Fragment != \"\" {\n    return fmt.Errorf(\"base URL must not include a #fragment; got %q\", u.Fragment)\n}","typeGuard":"func hasFragment(raw string) bool {\n    u, err := url.Parse(raw)\n    return err == nil && u.Fragment != \"\"\n}","tryCatchPattern":"if err := ValidateBaseURL(raw); err != nil {\n    if strings.Contains(err.Error(), \"fragment\") {\n        log.Fatal(\"remove everything after '#' in base_url\")\n    }\n    log.Fatal(err)\n}","preventionTips":["When pasting URLs from browsers, strip any anchor (#...) before saving to config.","Treat base_url as an API endpoint, never a documentation link.","Run ValidateBaseURL in CI on all provider configs."],"tags":["go","url-validation","configuration"],"backgroundTag":"invalid-url-format","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}