grpc/grpc-go · error

rls: bad control channel service config

Error message

rls: bad control channel service config %q: %v

What it means

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.

Solutions

  1. Validate the routeLookupChannelServiceConfig value independently with grpc.NewClient and a service config parse, or with the clientconf test harness.
  2. If the control channel needs no special config, omit routeLookupChannelServiceConfig entirely (the empty case is allowed and skips this check).
  3. Ensure any LB policy referenced inside the control-channel config is blank-imported by the binary (e.g. _ 'google.golang.org/grpc/balancer/roundrobin').
  4. Simplify the control-channel config to a minimal valid service config and re-add fields one at a time to isolate the failing entry.

Example fix

// before: unknown policy on control channel
"routeLookupChannelServiceConfig": { "loadBalancingConfig": [ { "not_a_real_policy": {} } ] }

// after: omit it, or use a registered policy
// (omit the field entirely)
// or:
"routeLookupChannelServiceConfig": { "loadBalancingConfig": [ { "round_robin": {} } ] }
Defensive patterns

Strategy: validation

Validate before calling

// Validate the optional control-channel service config independently.
func validateControlChannelSC(raw json.RawMessage) error {
    if len(raw) == 0 {
        return nil // allowed: empty means no control-channel override
    }
    res := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(string(raw))
    return res.Err
}

Type guard

func isControlChannelSCValid(raw json.RawMessage) bool {
    if len(raw) == 0 {
        return true
    }
    res := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(string(raw))
    return res.Err == nil
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/b72f7ffa3506e067. Report an issue: GitHub.

Appendix: 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 0c51461d27)