{"record":{"id":"1badde3b52b03d68","repo":"JuliusBrussee/caveman","slug":"compat-request-url-is-missing","errorCode":null,"errorMessage":"compat request URL is missing","messagePattern":"compat request URL is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":444,"sourceCode":"\t\treturn fmt.Errorf(\"compat upstream name %q is reserved\", name)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc ValidateBaseURL(raw string) error {\n\t_, err := parseBaseURL(raw, \"compat\")\n\treturn err\n}\n\n// ResolveUpstreamURL intentionally ignores RouteContext.BaseURL. Named mounts\n// are configured as independent static upstreams (`compat:` in standalone or\n// CAVE_COMPAT_UPSTREAMS in managed mode); applying the generic project\n// openai_compatible override here would collapse every named route onto one\n// target. The default /compat/ adapter is the route that honors BaseURL.\nfunc (a namedAdapter) ResolveUpstreamURL(_ context.Context, req *http.Request, _ providers.RouteContext) (*url.URL, error) {\n\tif req == nil || req.URL == nil {\n\t\treturn nil, fmt.Errorf(\"compat request URL is missing\")\n\t}\n\tif err := validateCompatPath(req.URL.Path, req.URL.RawPath); err != nil {\n\t\treturn nil, err\n\t}\n\tbase, err := parseBaseURL(a.BaseURL, a.Provider)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !strings.HasPrefix(req.URL.Path, a.prefix+\"/\") {\n\t\treturn nil, fmt.Errorf(\"compat route %q does not match prefix %q\", req.URL.Path, a.prefix+\"/\")\n\t}\n\tpath := strings.TrimPrefix(req.URL.Path, a.prefix)\n\tbase.Path = joinCompatPath(base.Path, path)\n\tbase.RawPath = \"\"\n\tbase.RawQuery = joinCompatQuery(base.RawQuery, req.URL.RawQuery)\n\treturn base, nil\n}\n","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openaicompat/openaicompat.go#L426-L462","documentation":"ResolveUpstreamURL in the OpenAI-compatible adapter requires an *http.Request with a non-nil URL to compute the upstream target. If either is nil, there is nothing to resolve and the adapter fails closed rather than guessing. This is an internal contract check guarding the proxy request path.","triggerScenarios":"Calling ResolveUpstreamURL with a nil *http.Request, or with a request constructed without a URL (e.g. http.NewRequest returned an error that was ignored, or a hand-built &http.Request{} without setting URL).","commonSituations":"Middleware or tests building requests manually and forgetting to set the URL field; an earlier http.NewRequest failure silently swallowed upstream of the adapter; passing nil into the adapter from a custom route handler.","solutions":["Ensure the request passed to ResolveUpstreamURL was created via http.NewRequest and that its error return was checked","Set req.URL explicitly before calling the adapter (req.URL = parsedURL)","Log the error from http.NewRequest instead of discarding it","In tests, use httptest.NewRequest which always sets a URL"],"exampleFix":"// before\nreq, _ := http.NewRequestWithContext(ctx, \"POST\", rawURL, body)\nurl, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)\n\n// after\nreq, err := http.NewRequestWithContext(ctx, \"POST\", rawURL, body)\nif err != nil {\n    return err\n}\nurl, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)","handlingStrategy":"try-catch","validationCode":"if req == nil || req.URL == nil {\n    return fmt.Errorf(\"request must have a URL before resolving upstream\")\n}","typeGuard":"func hasURL(req *http.Request) bool { return req != nil && req.URL != nil }","tryCatchPattern":"u, err := adapter.ResolveUpstreamURL(ctx, req, routeCtx)\nif err != nil {\n    if strings.Contains(err.Error(), \"request URL is missing\") {\n        // rebuild the request via http.NewRequest and retry once\n    }\n    return err\n}","preventionTips":["Always create requests with http.NewRequest and check its error","Never pass nil requests into adapter code paths","Use httptest.NewRequest in tests"],"tags":["go","http","proxy","url"],"backgroundTag":"null-argument","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"}