{"record":{"id":"56c0ed1225df77c7","repo":"grpc/grpc-go","slug":"outlier-detection-child-balancer-q-not-registere","errorCode":null,"errorMessage":"outlier detection: child balancer %q not registered","messagePattern":"outlier detection: child balancer %q not registered","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/balancer/outlierdetection/balancer.go","lineNumber":298,"sourceCode":"\t\t\tb.unejectEndpoint(epInfo)\n\t\t}\n\t\t// \"Reset each endpoint's ejection time multiplier to 0.\"\n\t\tepInfo.ejectionTimeMultiplier = 0\n\t}\n}\n\nfunc (b *outlierDetectionBalancer) UpdateClientConnState(s balancer.ClientConnState) error {\n\tlbCfg, ok := s.BalancerConfig.(*LBConfig)\n\tif !ok {\n\t\tb.logger.Errorf(\"received config with unexpected type %T: %v\", s.BalancerConfig, s.BalancerConfig)\n\t\treturn balancer.ErrBadResolverState\n\t}\n\n\t// Reject whole config if child policy doesn't exist, don't persist it for\n\t// later.\n\tbb := balancer.Get(lbCfg.ChildPolicy.Name)\n\tif bb == nil {\n\t\treturn fmt.Errorf(\"outlier detection: child balancer %q not registered\", lbCfg.ChildPolicy.Name)\n\t}\n\n\t// It is safe to read b.cfg here without holding the mutex, as the only\n\t// write to b.cfg happens later in this function. This function is part of\n\t// the balancer.Balancer API, so it is guaranteed to be called in a\n\t// synchronous manner, so it cannot race with this read.\n\tif b.cfg == nil || b.cfg.ChildPolicy.Name != lbCfg.ChildPolicy.Name {\n\t\tif err := b.child.switchTo(bb); err != nil {\n\t\t\treturn fmt.Errorf(\"outlier detection: error switching to child of type %q: %v\", lbCfg.ChildPolicy.Name, err)\n\t\t}\n\t}\n\n\tb.mu.Lock()\n\t// Inhibit child picker updates until this UpdateClientConnState() call\n\t// completes. If needed, a picker update containing the no-op config bit\n\t// determined from this config and most recent state from the child will be\n\t// sent synchronously upward at the end of this UpdateClientConnState()\n\t// call.","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/balancer/outlierdetection/balancer.go#L280-L316","documentation":"Returned by outlier detection UpdateClientConnState (internal/xds/balancer/outlierdetection/balancer.go:297) when `balancer.Get(lbCfg.ChildPolicy.Name)` returns nil — i.e. the child LB policy named in the config is not a registered balancer. Outlier detection wraps a child policy and rejects the whole config up front if the child cannot be built.","triggerScenarios":"UpdateClientConnState is called with an LBConfig whose ChildPolicy.Name is not a registered balancer builder (e.g. \"my_custom_lb\" when only stock policies are registered, or a typo like \"roud_robin\").","commonSituations":"Control plane references an LB policy the binary doesn't support; a custom LB policy wasn't registered via balancer.Register or its init() wasn't imported; version skew where a newer policy name reaches an older client.","solutions":["Ensure the child policy name matches a registered balancer (e.g. round_robin, weighted_target)","Import the package of any custom child balancer so its init() registers it","Fix the control plane to only send policy names the client supports"],"exampleFix":"// before: custom policy never registered, config references \"acme_lb\"\n// after\nimport _ \"example.com/acme/grpcbal/acmelb\" // registers \"acme_lb\" via balancer.Register in init()","handlingStrategy":"validation","validationCode":"// Confirm the child policy is a registered balancer before applying.\nif lbCfg.ChildPolicy == nil || balancer.Get(lbCfg.ChildPolicy.Name) == nil {\n    return fmt.Errorf(\"child balancer %q is not registered\", childPolicyName(lbCfg))\n}","typeGuard":"func hasRegisteredChild(cfg *outlierdetection.LBConfig) bool {\n    return cfg != nil && cfg.ChildPolicy != nil && balancer.Get(cfg.ChildPolicy.Name) != nil\n}","tryCatchPattern":"if err := bal.UpdateClientConnState(state); err != nil {\n    if strings.Contains(err.Error(), \"not registered\") {\n        logger.Errorf(\"child policy unavailable; keeping previous config: %v\", err)\n    }\n    return err\n}","preventionTips":["Blanket-import custom balancer packages so init() registers them","Restrict the control plane to policy names the deployed client supports","Log child policy names at startup to catch version skew"],"tags":["grpc","xds","outlier-detection","load-balancing","balancer-registration","go"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}