grpc/grpc-go · error

child policy not registered

Error message

child policy %q not registered

What it means

In UpdateClientConnState (clusterimpl.go:410-412), cluster_impl resolves its child policy name via balancer.Get; if no builder was registered for that name, the entire config is rejected. The child policy is the actual load-distribution algorithm (round_robin, ring_hash, weighted_round_robin, least_request) that cluster_impl delegates endpoint selection to.

Solutions

  1. Add a blank import for the missing balancer package, e.g. `import _ "google.golang.org/grpc/balancer/ringhash"`.
  2. Confirm the ChildPolicy.Name string in the xDS Cluster resource exactly matches a registered balancer's Name().
  3. For custom balancers, ensure init() calls balancer.Register before any grpc.Dial.

Example fix

// before
import _ "google.golang.org/grpc/balancer/roundrobin"
// xDS Cluster resource requests policy: RING_HASH -> error
// after
import (
    _ "google.golang.org/grpc/balancer/roundrobin"
    _ "google.golang.org/grpc/balancer/ringhash"
)
Defensive patterns

Strategy: validation

Validate before calling

// Before sending config, ensure the child policy is registered.
func validateChildPolicyRegistered(name string) error {
    if balancer.Get(name) == nil {
        return fmt.Errorf("child policy %q is not registered; add a blank import for its package", name)
    }
    return nil
}

Prevention

When it happens

Trigger: newConfig.ChildPolicy.Name references a balancer name (e.g. "ring_hash_experimental", "least_request_experimental") for which no Builder was registered via balancer.Register at process start.

Common situations: xDS control plane configures a policy (RING_HASH, LEAST_REQUEST) that the client binary does not link in; misspelled policy name in the xDS Cluster resource; client built with a stripped gRPC that omits optional balancer plugins.

Related errors


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

Appendix: source

Thrown at internal/xds/balancer/clusterimpl/clusterimpl.go:412

	defer clientConnUpdateHook()

	b.mu.Lock()
	b.inhibitPickerUpdates = true
	b.mu.Unlock()
	if b.logger.V(2) {
		b.logger.Infof("Received configuration: %s", pretty.ToJSON(s.BalancerConfig))
	}
	newConfig, ok := s.BalancerConfig.(*LBConfig)
	if !ok {
		return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig)
	}

	// Need to check for potential errors at the beginning of this function, so
	// that on errors, we reject the whole config, instead of applying part of
	// it.
	bb := balancer.Get(newConfig.ChildPolicy.Name)
	if bb == nil {
		return fmt.Errorf("child policy %q not registered", newConfig.ChildPolicy.Name)
	}

	if b.xdsClient == nil {
		c := xdsclient.FromResolverState(s.ResolverState)
		if c == nil {
			return balancer.ErrBadResolverState
		}
		b.xdsClient = c
	}

	xdsConfig := xdsresource.XDSConfigFromResolverState(s.ResolverState)
	if xdsConfig == nil {
		b.logger.Warningf("Received balancer config with no xDS config")
		return balancer.ErrBadResolverState
	}
	clusterCfg := xdsConfig.Clusters[newConfig.Cluster]
	clusterUpdate := clusterCfg.Config.Cluster
	if err := b.handleSecurityConfig(clusterUpdate.SecurityCfg); err != nil {

View on GitHub (pinned to 0c51461d27)