grpc/grpc-go · error

unexpected balancer config with type: %T

Error message

unexpected balancer config with type: %T

What it means

Returned by xds_cluster_impl's UpdateClientConnState when s.BalancerConfig cannot be type-asserted to *clusterimpl.LBConfig (clusterimpl.go:402-404). The cluster_impl policy only accepts its own strongly-typed config carrying the cluster name, drop categories, security cfg, and child policy. Any other type means the upstream resolver/balancer produced the wrong shape and the whole update is rejected.

Source

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

	oldHI := b.xdsHIPtr.Swap(newHI)
	if oldHI != nil {
		oldHI.Decrement()
	}
	return nil
}

func (b *clusterImplBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
	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
	}

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Route the config through clusterimpl.ParseConfig(rawJSON) and pass the returned *LBConfig as BalancerConfig.
  2. If writing a custom parent balancer, never wrap or translate the config into a different type before calling UpdateClientConnState.
  3. Verify resolver and balancer plugin come from the same gRPC-go version.

Example fix

// before
b.UpdateClientConnState(balancer.ClientConnState{
    BalancerConfig: &SomeOtherConfig{}, // wrong type
})
// after
import "google.golang.org/grpc/internal/xds/balancer/clusterimpl"
cfg, err := clusterimpl.ParseConfig(rawJSON)
if err != nil { return err }
b.UpdateClientConnState(balancer.ClientConnState{
    BalancerConfig: cfg, // *clusterimpl.LBConfig
})
Defensive patterns

Strategy: type-guard

Validate before calling

// Before invoking UpdateClientConnState, ensure the config is *clusterimpl.LBConfig.
func validateClusterImplConfig(cfg serviceconfig.LoadBalancingConfig) error {
    if _, ok := cfg.(*clusterimpl.LBConfig); !ok {
        return fmt.Errorf("expected *clusterimpl.LBConfig, got %T", cfg)
    }
    return nil
}

Type guard

func isClusterImplConfig(cfg serviceconfig.LoadBalancingConfig) bool {
    _, ok := cfg.(*clusterimpl.LBConfig)
    return ok
}

Prevention

When it happens

Trigger: UpdateClientConnState is invoked on a cluster_impl balancer with s.BalancerConfig of any type other than *clusterimpl.LBConfig (e.g. *clustermanager.lbConfig, *priority.LBConfig, a raw struct, or nil where the assertion expects non-nil).

Common situations: A custom parent balancer that bypasses clusterimpl.ParseConfig and hands down a different struct; gRPC-go version drift where the LBConfig struct layout changed between the resolver and the balancer plugin; tests that construct ClientConnState manually with the wrong config type.

Related errors


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