grpc/grpc-go · error

xds_wrr_locality: invalid LBConfig: child policy field must…

Error message

xds_wrr_locality: invalid LBConfig: child policy field must be set

What it means

The xDS `xds_wrr_locality` (weighted-round-robin by locality) load balancer parses its JSON config via ParseConfig in internal/xds/balancer/wrrlocality/balancer.go:98. The balancer is a wrapper that delegates actual endpoint selection to a child policy (normally `weighted_target`), so its LBConfig struct requires a non-nil `ChildPolicy` field. If the unmarshaled config is nil or `ChildPolicy` is nil, this error is returned. gRPC throws it because without a child policy the WRR-locality balancer has nothing to route through, making the configuration semantically invalid per gRFC A52.

Solutions

  1. Inspect the resolved service config (enable gRPC logging at INFO/VERBOSITY=2) to see exactly what JSON is being fed to the wrr_locality balancer.
  2. Ensure the xDS server (Istio/Envoy/your control plane) emits a `childPolicy` (typically `{ "weighted_target": { ... } }`) inside every `xds_wrr_locality` config block.
  3. If you are building the LB config programmatically, always populate the `ChildPolicy` field with a valid weighted_target config before passing it to the balancer.
  4. Upgrade both the gRPC client and the xDS server to versions that are mutually compatible with gRFC A52 (xDS custom LB policies).

Example fix

// before (xDS server-side / manual JSON)
{"xds_wrr_locality_experimental": {}}

// after
{"xds_wrr_locality_experimental": {
  "childPolicy": [
    { "weighted_target": {
        "targets": { "localityA": { "weight": 1, "childPolicy": [{"round_robin": {}}] } }
      }
    }
  ]
}}
Defensive patterns

Strategy: validation

Validate before calling

// Validate a wrr_locality service-config JSON before passing it on.
func validateWRRLocalityConfig(rawJSON []byte) error {
    var cfg struct {
        ChildPolicy json.RawMessage `json:"childPolicy"`
    }
    if err := json.Unmarshal(rawJSON, &cfg); err != nil {
        return fmt.Errorf("invalid wrr_locality json: %w", err)
    }
    if len(strings.TrimSpace(string(cfg.ChildPolicy))) == 0 || string(cfg.ChildPolicy) == "null" {
        return errors.New("xds_wrr_locality: childPolicy is missing")
    }
    return nil
}

Type guard

// For callers building a wrrlocality.LBConfig programmatically.
func hasChildPolicy(cfg *wrrlocality.LBConfig) bool {
    return cfg != nil && cfg.ChildPolicy != nil && len(*cfg.ChildPolicy) > 0
}

Prevention

When it happens

Trigger: Triggered when the gRPC resolver or xDS layer hands the `xds_wrr_locality_experimental` balancer a service config JSON whose `childPolicy` field is missing, null, or an empty array. Specifically ParseConfig at line 92-100 fails the check `lbCfg == nil || lbCfg.ChildPolicy == nil` at line 97. This usually happens when an xDS server (or a manually-crafted service config) sends an `xds_wrr_locality` config block without a nested child policy.

Common situations: A misconfigured xDS management server returning an incomplete cluster/load-balancing configuration; upgrading the control plane to a version that emits wrr_locality but forgets the child policy; manually constructing an LB config in tests; a service-config JSON typo where `childPolicy` is omitted or misspelled (e.g. `child_policy`).

Related errors


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

Appendix: source

Thrown at internal/xds/balancer/wrrlocality/balancer.go:98

		return nil
	}
	wrrL := &wrrLocalityBalancer{
		child:       wtb,
		childParser: wtbCfgParser,
	}

	wrrL.logger = prefixLogger(wrrL)
	wrrL.logger.Infof("Created")
	return wrrL
}

func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	var lbCfg *LBConfig
	if err := json.Unmarshal(s, &lbCfg); err != nil {
		return nil, fmt.Errorf("xds_wrr_locality: invalid LBConfig: %s, error: %v", string(s), err)
	}
	if lbCfg == nil || lbCfg.ChildPolicy == nil {
		return nil, errors.New("xds_wrr_locality: invalid LBConfig: child policy field must be set")
	}
	return lbCfg, nil
}

type attributeKey struct{}

// Equal allows the values to be compared by Attributes.Equal.
func (a AddrInfo) Equal(o any) bool {
	oa, ok := o.(AddrInfo)
	return ok && oa.LocalityWeight == a.LocalityWeight
}

// AddrInfo is the locality weight of the locality an address is a part of.
type AddrInfo struct {
	LocalityWeight uint32
}

// SetAddrInfo returns a copy of endpoint in which the Attributes field is

View on GitHub (pinned to 0c51461d27)