grpc/grpc-go · error
rls: bad control channel service config %q: %v
Error message
rls: bad control channel service config %q: %v
What it means
Returned by rls.ParseConfig (config.go:165-169) when routeLookupChannelServiceConfig is non-empty and internal.ParseServiceConfig reports an error. The control channel is the separate channel RLS uses to talk to the RLS server, and its service config (if provided) must itself be a valid gRPC service config. The %q is the offending service-config string and %v the parse error.
Source
Thrown at balancer/rls/config.go:168
cfgJSON := &lbConfigJSON{}
if err := json.Unmarshal(c, cfgJSON); err != nil {
return nil, fmt.Errorf("rls: json unmarshal failed for service config %+v: %v", string(c), err)
}
m := protojson.UnmarshalOptions{DiscardUnknown: true}
rlsProto := &rlspb.RouteLookupConfig{}
if err := m.Unmarshal(cfgJSON.RouteLookupConfig, rlsProto); err != nil {
return nil, fmt.Errorf("rls: bad RouteLookupConfig proto %+v: %v", string(cfgJSON.RouteLookupConfig), err)
}
lbCfg, err := parseRLSProto(rlsProto)
if err != nil {
return nil, err
}
if sc := string(cfgJSON.RouteLookupChannelServiceConfig); sc != "" {
parsed := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)
if parsed.Err != nil {
return nil, fmt.Errorf("rls: bad control channel service config %q: %v", sc, parsed.Err)
}
lbCfg.controlChannelServiceConfig = sc
}
if cfgJSON.ChildPolicyConfigTargetFieldName == "" {
return nil, fmt.Errorf("rls: childPolicyConfigTargetFieldName field is not set in service config %+v", string(c))
}
name, config, err := parseChildPolicyConfigs(cfgJSON.ChildPolicy, cfgJSON.ChildPolicyConfigTargetFieldName)
if err != nil {
return nil, err
}
lbCfg.childPolicyName = name
lbCfg.childPolicyConfig = config
lbCfg.childPolicyTargetField = cfgJSON.ChildPolicyConfigTargetFieldName
return lbCfg, nil
}
func parseRLSProto(rlsProto *rlspb.RouteLookupConfig) (*lbConfig, error) {View on GitHub (pinned to 03255a9237)
Solutions
- Read the %v parse error and fix the offending field in the control-channel service config.
- If you do not need a custom control-channel service config, omit routeLookupChannelServiceConfig entirely.
- Validate the string with grpc's own service-config parser semantics (retry/methodConfig/loadBalancingConfig) before shipping.
Example fix
// before: invalid service config for the control channel
cfg.RouteLookupChannelServiceConfig = `{ "loadBalancingConfig": [ { "not_a_policy": {} } ] }`
// after: valid policy (or omit the field)
cfg.RouteLookupChannelServiceConfig = `{ "loadBalancingConfig": [ { "round_robin": {} } ] }` Defensive patterns
Strategy: validation
Validate before calling
// Reuse grpc's parser: this mirrors what ParseConfig does internally.
import "google.golang.org/grpc/internal"
func validateControlChannelSC(sc string) error {
if sc == "" { return nil }
pr := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(sc)
return pr.Err
} Prevention
- Omit routeLookupChannelServiceConfig unless you need to tune the control channel.
- When set, use a minimal valid service config (e.g. just a loadBalancingConfig).
When it happens
Trigger: The optional routeLookupChannelServiceConfig field is set but is not valid gRPC service-config JSON (e.g. unknown methodConfig, bad loadBalancingConfig, syntax error).
Common situations: Copy-pasting a loadBalancingConfig into routeLookupChannelServiceConfig. Providing a config meant for the data channel, not the control channel. Control-plane emitting an outdated service-config schema.
Related errors
- rls: json unmarshal failed for service config %+v: %v
- rls: bad RouteLookupConfig proto %+v: %v
- rls: childPolicyConfigTargetFieldName field is not set in se
- rls: empty lookup_service in route lookup config %+v
- rls: invalid target URI in lookup_service %s
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/b72f7ffa3506e067.
Report an issue: GitHub.