{"record":{"id":"b72f7ffa3506e067","repo":"grpc/grpc-go","slug":"rls-bad-control-channel-service-config-q-v","errorCode":null,"errorMessage":"rls: bad control channel service config %q: %v","messagePattern":"rls: bad control channel service config %q: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"balancer/rls/config.go","lineNumber":168,"sourceCode":"\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)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlbCfg.childPolicyName = name\n\tlbCfg.childPolicyConfig = config\n\tlbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName\n\treturn lbCfg, nil\n}\n\nfunc parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/rls/config.go#L150-L186","documentation":"Emitted from (rlsBB).ParseConfig (config.go:168) when routeLookupChannelServiceConfig is non-empty but internal.ParseServiceConfig reports an error. This service config is applied to the control channel (the gRPC channel the RLS balancer uses to talk to the Route Lookup Server), so it must itself be a valid gRPC service config.","triggerScenarios":"Providing a routeLookupChannelServiceConfig blob that is invalid JSON or references an unknown load balancing policy, an unknown retry policy, or otherwise fails gRPC service-config parsing.","commonSituations":"Setting a control-channel LB policy (e.g. 'xdsmetadata_experimental', 'grpclb') that the client binary does not have linked in; malformed JSON inside the nested config; copying a config intended for the data channel that includes fields the control channel rejects.","solutions":["Validate the routeLookupChannelServiceConfig value independently with grpc.NewClient and a service config parse, or with the clientconf test harness.","If the control channel needs no special config, omit routeLookupChannelServiceConfig entirely (the empty case is allowed and skips this check).","Ensure any LB policy referenced inside the control-channel config is blank-imported by the binary (e.g. _ 'google.golang.org/grpc/balancer/roundrobin').","Simplify the control-channel config to a minimal valid service config and re-add fields one at a time to isolate the failing entry."],"exampleFix":"// before: unknown policy on control channel\n\"routeLookupChannelServiceConfig\": { \"loadBalancingConfig\": [ { \"not_a_real_policy\": {} } ] }\n\n// after: omit it, or use a registered policy\n// (omit the field entirely)\n// or:\n\"routeLookupChannelServiceConfig\": { \"loadBalancingConfig\": [ { \"round_robin\": {} } ] }","handlingStrategy":"validation","validationCode":"// Validate the optional control-channel service config independently.\nfunc validateControlChannelSC(raw json.RawMessage) error {\n    if len(raw) == 0 {\n        return nil // allowed: empty means no control-channel override\n    }\n    res := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(string(raw))\n    return res.Err\n}","typeGuard":"func isControlChannelSCValid(raw json.RawMessage) bool {\n    if len(raw) == 0 {\n        return true\n    }\n    res := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(string(raw))\n    return res.Err == nil\n}","tryCatchPattern":null,"preventionTips":["Omit routeLookupChannelServiceConfig when the control channel needs no special policy.","Blank-import any LB policy referenced inside the control-channel config.","Validate nested service configs with the same parser the channel uses before deploying."],"tags":["rls","control-channel","service-config","validation"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}