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
- 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.
- Ensure the xDS server (Istio/Envoy/your control plane) emits a `childPolicy` (typically `{ "weighted_target": { ... } }`) inside every `xds_wrr_locality` config block.
- 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.
- 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
- Always pair xds_wrr_locality with a weighted_target child policy in your xDS/control-plane config generator.
- Add a config-validation step in CI that rejects wrr_locality blocks without a child policy.
- Log the resolved service config at INFO before it reaches the balancer so a missing child policy is visible early.
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
- lrsclient: transport builder is nil
- no priority is provided, all priorities are removed
- xds_wrr_locality: config generated
- xds_wrr_locality: error marshalling prepared config
- xds_wrr_locality: invalid LBConfig
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 isView on GitHub (pinned to 0c51461d27)