grpc/grpc-go · error

rls: bad RouteLookupConfig proto %+v: %v

Error message

rls: bad RouteLookupConfig proto %+v: %v

What it means

Returned by rls.ParseConfig (config.go:155-159) when the routeLookupConfig bytes cannot be unmarshalled via protojson into rlspb.RouteLookupConfig (with DiscardUnknown=true). This means the JSON is valid JSON but does not match the RouteLookupConfig proto schema (wrong field names, bad nested structure, invalid enum/Duration encoding). The %+v is the raw routeLookupConfig string.

Source

Thrown at balancer/rls/config.go:158

//	- childPolicy:
//	  - must find a valid child policy with a valid config
//
//	- childPolicyConfigTargetFieldName:
//	  - must be set and non-empty
func (rlsBB) ParseConfig(c json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	if logger.V(2) {
		logger.Infof("Received JSON service config: %v", pretty.ToJSON(c))
	}

	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)

View on GitHub (pinned to 03255a9237)

Solutions

  1. Compare the routeLookupConfig JSON against the grpc.lookup.v1.RouteLookupConfig proto definition; fix mismatched field names/types.
  2. For google.protobuf.Duration fields use the "Ns" or seconds+nanos JSON form protojson expects.
  3. Regenerate the config from a known-good RouteLookupConfig proto instance (protojson.Marshal) and diff.
  4. Check the xDS control plane's RLS proto version against the client's.

Example fix

// before: routeLookupConfig with wrong duration encoding or bad field name
rlc := `{ "lookupService": "rls.example", "grpcKeybuilders": [...], "maxAge": "5m" }` // 'maxAge' wrong shape

// after: build it from a typed proto and marshal
b, _ := protojson.Marshal(&rlspb.RouteLookupConfig{ LookupService: "rls.example", MaxAge: durationpb.New(5*time.Minute) })
Defensive patterns

Strategy: validation

Validate before calling

import "google.golang.org/protobuf/encoding/protojson"
import rlsproto "...grpc_lookup_v1"

func validateRouteLookupConfig(raw []byte) error {
    var p rlsproto.RouteLookupConfig
    return protojson.UnmarshalOptions{DiscardUnknown: true}.Unmarshal(raw, &p)
}

Prevention

When it happens

Trigger: routeLookupConfig is valid JSON but uses field names that are not in grpc.lookup.v1.RouteLookupConfig, or encodes a duration/enum in a non-canonical way that protojson rejects.

Common situations: Control plane translating RLS config with a stale or wrong proto. Hand-writing the proto JSON with camelCase vs snake_case confusion (protojson accepts both, but typos are rejected). Including unknown fields (allowed due to DiscardUnknown, so this is usually a real schema mismatch).

Related errors


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