{"record":{"id":"18572442082d13e9","repo":"JuliusBrussee/caveman","slug":"compat-route-q-does-not-match-default-compat-mo","errorCode":null,"errorMessage":"compat route %q does not match default /compat/ mount","messagePattern":"compat route %q does not match default /compat/ mount","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openaicompat/openaicompat.go","lineNumber":44,"sourceCode":"// keeps direct adapter users fail-closed as well.\nfunc (a Adapter) MatchRoute(method, path string) bool {\n\tif err := validateCompatPath(path, \"\"); err != nil {\n\t\treturn false\n\t}\n\treturn a.Base.MatchRoute(method, path)\n}\n\n// ResolveUpstreamURL removes only the default public /compat mount before\n// resolving the provider path. Named adapters and the reserved legacy routes\n// keep their dedicated resolver/compatibility behavior; this method exists for\n// the bare providers.openai_compatible route only.\nfunc (a Adapter) ResolveUpstreamURL(_ context.Context, req *http.Request, route providers.RouteContext) (*url.URL, error) {\n\tif req == nil || req.URL == nil || !strings.HasPrefix(req.URL.Path, \"/compat/\") {\n\t\tpath := \"\"\n\t\tif req != nil && req.URL != nil {\n\t\t\tpath = req.URL.Path\n\t\t}\n\t\treturn nil, fmt.Errorf(\"compat route %q does not match default /compat/ mount\", path)\n\t}\n\tif err := validateCompatPath(req.URL.Path, req.URL.RawPath); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// /compat/stub and /compat/openai-compatible were the original default\n\t// routes. They share the same strict path and query handling as the bare\n\t// mount, but retain their historical prefix stripping.\n\trequestPath := strings.TrimPrefix(req.URL.Path, \"/compat\")\n\tfor _, legacy := range []string{\"/stub\", \"/openai-compatible\"} {\n\t\tif hasCompatPrefix(req.URL.Path, \"/compat\"+legacy) {\n\t\t\trequestPath = strings.TrimPrefix(req.URL.Path, \"/compat\"+legacy)\n\t\t\tbreak\n\t\t}\n\t}\n\n\tbaseURL := a.Base.BaseURL\n\tif route.BaseURL != \"\" {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/providers/openaicompat/openaicompat.go#L26-L62","documentation":"Thrown by the default OpenAI-compatible adapter's ResolveUpstreamURL when the incoming request path does not start with /compat/. This adapter exists only for the bare public /compat/ mount; requests arriving on any other path indicate a routing/mount misconfiguration and are rejected rather than proxied to a guessed upstream.","triggerScenarios":"Calling Adapter.ResolveUpstreamURL with req.URL.Path like \"/v1/chat/completions\", \"/\", \"\", or a named-mount path like \"/compat-foo/...\" — anything without the \"/compat/\" prefix. Also fires when req or req.URL is nil (the guard reports the empty/blank path).","commonSituations":"A gateway route table sending openai_compatible traffic to the default adapter instead of a named adapter; direct callers reusing the default adapter for a custom mount path; a rewrite rule stripping /compat/ before the adapter runs.","solutions":["Route the request through the /compat/ mount so the path retains the /compat/ prefix when ResolveUpstreamURL runs.","For named mounts (e.g. /compat/<name>), use the namedAdapter registered for that mount — it resolves via its own configured BaseURL, not this default adapter.","Check gateway routing config: the bare providers.openai_compatible route must map to the default adapter, custom routes to named adapters."],"exampleFix":"// before: default adapter given a bare route\nurl, err := defaultAdapter.ResolveUpstreamURL(ctx, req, route) // req.URL.Path = \"/v1/chat/completions\"\n\n// after: request reaches the adapter on the public mount\n// req.URL.Path = \"/compat/v1/chat/completions\"\nurl, err := defaultAdapter.ResolveUpstreamURL(ctx, req, route)","handlingStrategy":"validation","validationCode":"func isDefaultCompatMount(path string) bool {\n    return strings.HasPrefix(path, \"/compat/\")\n}\n\n// before dispatching to the default adapter:\nif !isDefaultCompatMount(req.URL.Path) {\n    return fmt.Errorf(\"route %q must go to a named adapter, not the default /compat/ adapter\", req.URL.Path)\n}","typeGuard":null,"tryCatchPattern":"Map to an HTTP routing error (404/421) at the gateway layer; log the offending path. Deterministic misroute — retrying cannot help.","preventionTips":["Keep route registration and adapter selection in one table so /compat/* paths always reach an adapter whose prefix matches.","Cover each mount path with a routing unit test asserting which adapter is selected.","Document that the default adapter serves only the bare /compat/ mount."],"tags":["openai-compat","routing","reverse-proxy","configuration"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}