grpc/grpc-go · error

rls: invalid childPolicy: entry %v does not contain exactly

Error message

rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q

What it means

The childPolicy field in an RLS service config is a list of single-entry maps, each mapping one LB policy name to its config (the prioritized child policy list). parseChildPolicyConfigs requires every list entry to have exactly one policy->config key; an entry with zero or multiple keys is ambiguous and rejected.

Source

Thrown at balancer/rls/config.go:287

		cacheSizeBytes = maxCacheSize
	}
	return &lbConfig{
		kbMap:                kbMap,
		lookupService:        lookupService,
		lookupServiceTimeout: lookupServiceTimeout,
		maxAge:               maxAge,
		staleAge:             staleAge,
		cacheSizeBytes:       cacheSizeBytes,
		defaultTarget:        rlsProto.GetDefaultTarget(),
	}, nil
}

// parseChildPolicyConfigs iterates through the list of child policies and picks
// the first registered policy and validates its config.
func parseChildPolicyConfigs(childPolicies []map[string]json.RawMessage, targetFieldName string) (string, map[string]json.RawMessage, error) {
	for i, config := range childPolicies {
		if len(config) != 1 {
			return "", nil, fmt.Errorf("rls: invalid childPolicy: entry %v does not contain exactly 1 policy/config pair: %q", i, config)
		}

		var name string
		var rawCfg json.RawMessage
		for name, rawCfg = range config {
		}
		builder := balancer.Get(name)
		if builder == nil {
			continue
		}
		parser, ok := builder.(balancer.ConfigParser)
		if !ok {
			return "", nil, fmt.Errorf("rls: childPolicy %q with config %q does not support config parsing", name, string(rawCfg))
		}

		// To validate child policy configs we do the following:
		// - unmarshal the raw JSON bytes of the child policy config into a map
		// - add an entry with key set to `target_field_name` and a dummy value

View on GitHub (pinned to 03255a9237)

Solutions

  1. Make each array element a single-key object: [{"pick_first":{}}, {"round_robin":{}}] for fallback ordering, not [{"pick_first":{},"round_robin":{}}].
  2. Remove any empty {} entries from the ChildPolicy array.
  3. Validate the array shape before deploying: every element must satisfy len(element)==1.

Example fix

// before
"childPolicy": [
  {"pick_first": {}, "round_robin": {}}
]
// after
"childPolicy": [
  {"pick_first": {}},
  {"round_robin": {}}
]
Defensive patterns

Strategy: validation

Validate before calling

func validateChildPolicyShape(childPolicy []map[string]json.RawMessage) error {
    if len(childPolicy) == 0 {
        return fmt.Errorf("childPolicy list is empty")
    }
    for i, entry := range childPolicy {
        if len(entry) != 1 {
            return fmt.Errorf("childPolicy[%d] must have exactly 1 policy/config pair, got %d", i, len(entry))
        }
    }
    return nil
}

Prevention

When it happens

Trigger: A ChildPolicy JSON array entry that is {} (empty object) or {"pick_first":{...},"round_robin":{...}} (two policies in one entry). The error reports the offending entry index and the map contents.

Common situations: Hand-authoring the child policy list as a flat object instead of a list of single-key maps; YAML->JSON conversion collapsing structure; misunderstanding the gRPC LB policy grammar where each priority is a separate one-element map.

Related errors


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