{"record":{"id":"d98b592eaa489871","repo":"grpc/grpc-go","slug":"xds-wrr-locality-invalid-lbconfig-child-policy-f","errorCode":null,"errorMessage":"xds_wrr_locality: invalid LBConfig: child policy field must be set","messagePattern":"xds_wrr_locality: invalid LBConfig: child policy field must be set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/balancer/wrrlocality/balancer.go","lineNumber":98,"sourceCode":"\t\treturn nil\n\t}\n\twrrL := &wrrLocalityBalancer{\n\t\tchild:       wtb,\n\t\tchildParser: wtbCfgParser,\n\t}\n\n\twrrL.logger = prefixLogger(wrrL)\n\twrrL.logger.Infof(\"Created\")\n\treturn wrrL\n}\n\nfunc (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {\n\tvar lbCfg *LBConfig\n\tif err := json.Unmarshal(s, &lbCfg); err != nil {\n\t\treturn nil, fmt.Errorf(\"xds_wrr_locality: invalid LBConfig: %s, error: %v\", string(s), err)\n\t}\n\tif lbCfg == nil || lbCfg.ChildPolicy == nil {\n\t\treturn nil, errors.New(\"xds_wrr_locality: invalid LBConfig: child policy field must be set\")\n\t}\n\treturn lbCfg, nil\n}\n\ntype attributeKey struct{}\n\n// Equal allows the values to be compared by Attributes.Equal.\nfunc (a AddrInfo) Equal(o any) bool {\n\toa, ok := o.(AddrInfo)\n\treturn ok && oa.LocalityWeight == a.LocalityWeight\n}\n\n// AddrInfo is the locality weight of the locality an address is a part of.\ntype AddrInfo struct {\n\tLocalityWeight uint32\n}\n\n// SetAddrInfo returns a copy of endpoint in which the Attributes field is","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/balancer/wrrlocality/balancer.go#L80-L116","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`).","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)."],"exampleFix":"// before (xDS server-side / manual JSON)\n{\"xds_wrr_locality_experimental\": {}}\n\n// after\n{\"xds_wrr_locality_experimental\": {\n  \"childPolicy\": [\n    { \"weighted_target\": {\n        \"targets\": { \"localityA\": { \"weight\": 1, \"childPolicy\": [{\"round_robin\": {}}] } }\n      }\n    }\n  ]\n}}","handlingStrategy":"validation","validationCode":"// Validate a wrr_locality service-config JSON before passing it on.\nfunc validateWRRLocalityConfig(rawJSON []byte) error {\n    var cfg struct {\n        ChildPolicy json.RawMessage `json:\"childPolicy\"`\n    }\n    if err := json.Unmarshal(rawJSON, &cfg); err != nil {\n        return fmt.Errorf(\"invalid wrr_locality json: %w\", err)\n    }\n    if len(strings.TrimSpace(string(cfg.ChildPolicy))) == 0 || string(cfg.ChildPolicy) == \"null\" {\n        return errors.New(\"xds_wrr_locality: childPolicy is missing\")\n    }\n    return nil\n}","typeGuard":"// For callers building a wrrlocality.LBConfig programmatically.\nfunc hasChildPolicy(cfg *wrrlocality.LBConfig) bool {\n    return cfg != nil && cfg.ChildPolicy != nil && len(*cfg.ChildPolicy) > 0\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["grpc","xds","load-balancer","wrr-locality","config-validation"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}