{"record":{"id":"16315a81aa37a37d","repo":"grpc/grpc-go","slug":"rls-bad-routelookupconfig-proto-v-v","errorCode":null,"errorMessage":"rls: bad RouteLookupConfig proto %+v: %v","messagePattern":"rls: bad RouteLookupConfig proto %\\+v: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/config.go","lineNumber":158,"sourceCode":"//\t- childPolicy:\n//\t  - must find a valid child policy with a valid config\n//\n//\t- childPolicyConfigTargetFieldName:\n//\t  - must be set and non-empty\nfunc (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {\n\tif logger.V(2) {\n\t\tlogger.Infof(\"Received JSON service config: %v\", pretty.ToJSON(c))\n\t}\n\n\tcfgJSON := &lbConfigJSON{}\n\tif err := json.Unmarshal(c, cfgJSON); err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: json unmarshal failed for service config %+v: %v\", string(c), err)\n\t}\n\n\tm := protojson.UnmarshalOptions{DiscardUnknown: true}\n\trlsProto := &rlspb.RouteLookupConfig{}\n\tif err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {\n\t\treturn nil, fmt.Errorf(\"rls: bad RouteLookupConfig proto %+v: %v\", string(cfgJSON.RouteLookupConfig), err)\n\t}\n\tlbCfg, err := parseRLSProto(rlsProto)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != \"\" {\n\t\tparsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)\n\t\tif parsed.Err != nil {\n\t\t\treturn nil, fmt.Errorf(\"rls: bad control channel service config %q: %v\", sc, parsed.Err)\n\t\t}\n\t\tlbCfg.controlChannelServiceConfig = sc\n\t}\n\n\tif cfgJSON.ChildPolicyConfigTargetFieldName == \"\" {\n\t\treturn nil, fmt.Errorf(\"rls: childPolicyConfigTargetFieldName field is not set in service config %+v\", string(c))\n\t}\n\tname, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName)","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/config.go#L140-L176","documentation":"Emitted from (rlsBB).ParseConfig (config.go:158) when protojson.Unmarshal of the routeLookupConfig JSON blob into rlspb.RouteLookupConfig fails. UnmarshalOptions has DiscardUnknown:true, so unknown fields are ignored; the error fires only for genuine proto incompatibilities such as a field whose JSON value type does not match the proto field type, or a malformed well-known type like a duration/struct value.","triggerScenarios":"The routeLookupConfig object contains a field with the wrong JSON type, e.g. grpc_keybuilders as a string instead of an array, or a numeric field given an object; or an embedded google.protobuf.Duration/Struct field carrying an invalid JSON encoding.","commonSituations":"Proto schema version skew between the config producer (xDS server, control plane) and the client's grpc_lookup_v1 proto; a field renamed/retyped across versions (e.g. an older field shape still emitted); a control-plane bug serializing a Go struct directly instead of the proto.","solutions":["Compare the proto definition used by the config producer against google.golang.org/grpc/internal/proto/grpc_lookup_v1/rls_config.pb.go on the client.","Reproduce by unmarshaling the routeLookupConfig blob in isolation with protojson and read the field-level error message in %v.","Upgrade the client (or the producer) so both sides use the same RouteLookupConfig schema version.","Strip any non-proto JSON fields from routeLookupConfig and ensure well-known types (Duration, Struct) use their canonical JSON form."],"exampleFix":"// before: grpc_keybuilders given as an object instead of an array\n\"routeLookupConfig\": { \"grpcKeybuilders\": { \"names\": [ { \"service\": \"svc\" } ] } }\n\n// after: grpc_keybuilders is an array per the proto\n\"routeLookupConfig\": {\n  \"grpcKeybuilders\": [ { \"names\": [ { \"service\": \"pkg.svc\" } ], \"headers\": [ { \"key\": \"k\" } ] } ]\n}","handlingStrategy":"validation","validationCode":"// Confirm the routeLookupConfig blob parses as the proto on the client build.\nfunc validateRouteLookupProto(raw json.RawMessage) error {\n    var p rlspb.RouteLookupConfig\n    opts := protojson.UnmarshalOptions{DiscardUnknown: true}\n    if err := opts.Unmarshal(raw, &p); err != nil {\n        return fmt.Errorf(\"routeLookupConfig is not a valid RouteLookupConfig proto: %w\", err)\n    }\n    return nil\n}","typeGuard":"func isRouteLookupProto(raw json.RawMessage) bool {\n    var p rlspb.RouteLookupConfig\n    return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, &p) == nil\n}","tryCatchPattern":null,"preventionTips":["Keep the config producer and client on the same grpc_lookup_v1 proto version.","Use protojson.Marshal on the producer side to generate the routeLookupConfig blob rather than hand-serializing.","Add a contract test that round-trips the produced config through the client's ParseConfig."],"tags":["rls","proto","protojson","validation","service-config"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}