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

  1. Read the %v parse error and fix the offending field in the control-channel service config.
  2. If you do not need a custom control-channel service config, omit routeLookupChannelServiceConfig entirely.
  3. 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

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


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