{"record":{"id":"d4eb230f89d49aea","repo":"JuliusBrussee/caveman","slug":"compat-route-q-does-not-match-prefix-q","errorCode":null,"errorMessage":"compat route %q does not match prefix %q","messagePattern":"compat route %q does not match prefix %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":236,"sourceCode":"\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\n// ValidateRequestPath rejects ambiguous path encodings before adapter selection.\n// Gateways call this with the original URL (including RawPath), while the\n// adapter MatchRoute/ResolveUpstreamURL checks provide a second fail-closed\n// boundary for direct callers.\nfunc ValidateRequestPath(u *url.URL) error {\n\tif u == nil {\n\t\treturn fmt.Errorf(\"request URL is missing\")\n\t}\n\tif !strings.HasPrefix(u.Path, \"/compat/\") {\n\t\treturn nil","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/providers/openaicompat/openaicompat.go#L218-L254","documentation":"Thrown by namedAdapter.ResolveUpstreamURL when the request path does not start with the adapter's configured prefix plus a slash (e.g. \"/compat/myupstream/\"). Named mounts own a distinct path prefix; a request that reaches the wrong named adapter, or omits the trailing mount segment, is rejected rather than proxied with an unintended path.","triggerScenarios":"A named adapter configured with prefix \"/compat/groq\" receiving a request for \"/compat/groq\" (no trailing slash + suffix), \"/compat/openrouter/...\", or \"/compat/...\" — any path lacking the exact \"prefix + \"/\"\" start.","commonSituations":"Gateway route table mapping two named mounts to each other's adapters; renaming a mount in config while the route registration kept the old prefix; a client hitting the bare mount path but the named adapter being selected.","solutions":["Send requests to the full path under the named mount: /compat/<name>/<rest-of-path> (e.g. /compat/groq/v1/chat/completions).","Verify the named adapter's configured prefix matches the route the gateway dispatches to it — both come from the same compat upstream definition.","If the intent was the default /compat/ behavior (provider BaseURL override), use the default Adapter, not a namedAdapter."],"exampleFix":"# before: request to bare mount handled by named adapter\nPOST /compat/v1/chat/completions\n\n# after: request under the named mount\nPOST /compat/groq/v1/chat/completions","handlingStrategy":"validation","validationCode":"func matchesNamedMount(path, prefix string) bool {\n    return strings.HasPrefix(path, prefix+\"/\")\n}","typeGuard":null,"tryCatchPattern":"Return 404 at the gateway for the mismatched path; log the adapter's prefix and the request path together to make the misroute obvious.","preventionTips":["Derive the named adapter's prefix and the gateway route from the same config value, never two copies.","When renaming a compat upstream, update routes, config, and client docs in one change.","Assert one positive and one negative path per named mount in routing tests."],"tags":["openai-compat","routing","configuration","reverse-proxy"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}