{"record":{"id":"805ed3ae95796981","repo":"JuliusBrussee/caveman","slug":"bedrock-configured-endpoint-kind-q-does-not-allow","errorCode":null,"errorMessage":"bedrock configured endpoint kind %q does not allow request kind %q","messagePattern":"bedrock configured endpoint kind %q does not allow request kind %q","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/bedrock/routing.go","lineNumber":163,"sourceCode":"// quotas, features, stream wire, and AWS invocation-logging support differ from\n// Runtime. Both surfaces remain provider=bedrock in telemetry.\nfunc (a Adapter) ResolveUpstreamURL(ctx context.Context, req *http.Request, route providers.RouteContext) (*url.URL, error) {\n\tbaseURL := a.BaseURL\n\tif route.BaseURL != \"\" {\n\t\tbaseURL = route.BaseURL\n\t}\n\tbase, err := url.Parse(baseURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"bedrock base url invalid: %w\", err)\n\t}\n\tregion, err := resolveRegion(req, base)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkind := endpointKindForPath(req.URL.Path)\n\tif configuredKind := strings.ToLower(strings.TrimSpace(route.EndpointKind)); configuredKind != \"\" && configuredKind != kind {\n\t\treturn nil, fmt.Errorf(\"bedrock configured endpoint kind %q does not allow request kind %q\", configuredKind, kind)\n\t}\n\tswitch kind {\n\tcase endpointRuntime:\n\t\tif !RegionAllowed(region) {\n\t\t\treturn nil, fmt.Errorf(\"bedrock region %q is not on the allowlist\", region)\n\t\t}\n\t\tmodelID, action := parseModelPath(req.URL.Path)\n\t\tif modelID == \"\" || action == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"bedrock request path %q does not name a model and action\", req.URL.Path)\n\t\t}\n\t\tif !actionAllowed(action) {\n\t\t\treturn nil, fmt.Errorf(\"bedrock action %q is not allowed\", action)\n\t\t}\n\t\tif !modelAllowed(modelID) {\n\t\t\treturn nil, fmt.Errorf(\"bedrock model %q is not on the allowlist\", modelID)\n\t\t}\n\t\tbase.Path = strings.TrimRight(base.Path, \"/\") + strings.TrimPrefix(req.URL.Path, \"/bedrock\")\n\tcase endpointMantle:","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/providers/bedrock/routing.go#L145-L181","documentation":"Bedrock classifies each request path as runtime or mantle endpoint kind. If a route was explicitly configured with an EndpointKind and the incoming request's path resolves to a different kind, the adapter refuses to forward — a mantle-configured route must not silently serve runtime-shaped requests (their IAM services, quotas, and wire behavior differ). Both surfaces still report provider=bedrock in telemetry; this is a per-route safety check.","triggerScenarios":"Configuring a route with endpoint_kind: mantle (or runtime) and then sending a request whose path matches the other kind — e.g. a mantle route receiving /bedrock/anthropic/v1/messages or a runtime route receiving a mantle-style path.","commonSituations":"Copy-pasting a route block from a mantle deployment and pointing existing runtime clients at it; enabling mantle opt-in then forgetting to update client base paths; case/whitespace differences are tolerated (the check lower-cases and trims) but kind mismatch is not.","solutions":["Align the client's request path with the configured endpoint kind: use mantle-shaped paths only on mantle routes, runtime-shaped paths only on runtime routes.","If the route should serve both, leave endpoint_kind unset — an empty configured kind allows either.","Check for accidental whitespace/casing in the configured kind; ' Mantle ' trims to 'mantle' but 'mantel' will never match and effectively forces the mismatch branch."],"exampleFix":"# before (caveman.yaml)\nroutes:\n  bedrock-main:\n    provider: bedrock\n    endpoint_kind: mantle\n# client still calls: POST /bedrock/model/anthropic.claude.../invoke  (runtime path)\n\n# after\nroutes:\n  bedrock-main:\n    provider: bedrock\n    endpoint_kind: runtime   # matches the client's runtime-shaped path","handlingStrategy":"validation","validationCode":"// Mirror the adapter's kind classification before sending.\nfunc requestKind(path string) string {\n    if strings.Contains(path, \"/bedrock/\") && !strings.Contains(path, \"anthropic/\") {\n        return \"runtime\" // adjust to your routing table's shape\n    }\n    return \"mantle\"\n}\nconfigured := strings.ToLower(strings.TrimSpace(route.EndpointKind))\nif configured != \"\" && configured != requestKind(req.URL.Path) {\n    return fmt.Errorf(\"route kind %q does not match request path %q\", configured, req.URL.Path)\n}","typeGuard":"func routeKindMatches(configured, path string) bool {\n    c := strings.ToLower(strings.TrimSpace(configured))\n    return c == \"\" || c == requestKind(path)\n}","tryCatchPattern":"if _, err := adapter.ResolveUpstreamURL(ctx, req, route); err != nil {\n    if strings.Contains(err.Error(), \"does not allow request kind\") {\n        http.Error(w, \"request path does not match the route's configured bedrock endpoint kind\", http.StatusBadRequest)\n        return\n    }\n    http.Error(w, err.Error(), http.StatusBadRequest)\n}","preventionTips":["Leave endpoint_kind unset on routes that must serve both surfaces.","Keep client base paths and route endpoint_kind in the same config doc.","Remember the comparison is case-insensitive after trimming, but the kinds themselves are fixed strings."],"tags":["go","bedrock","routing","configuration","aws"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}