grpc/grpc-go · error
unexpected balancer config with type: %T
Error message
unexpected balancer config with type: %T
What it means
cluster_manager's UpdateClientConnState asserts s.BalancerConfig is *clustermanager.lbConfig (clustermanager.go:171-174). Any other type is rejected because cluster_manager needs the Children map and per-child policy configs to drive its sub-balancers via balancergroup.
Source
Thrown at internal/xds/balancer/clustermanager/clustermanager.go:173
// If multiple sub-balancers run into errors, we will return only the last
// one, which is still good enough, since the grpc channel will anyways
// return this error as balancer.ErrBadResolver to the name resolver,
// resulting in re-resolution attempts.
return retErr
// Adding or removing a sub-balancer will result in the
// needUpdateStateOnResume bit to true which results in a picker update once
// resumeStateUpdates() is called.
}
func (b *bal) UpdateClientConnState(s balancer.ClientConnState) error {
if b.logger.V(2) {
b.logger.Infof("Received update from resolver, balancer config: %+v", pretty.ToJSON(s.BalancerConfig))
}
newConfig, ok := s.BalancerConfig.(*lbConfig)
if !ok {
return fmt.Errorf("unexpected balancer config with type: %T", s.BalancerConfig)
}
b.stateAggregator.pauseStateUpdates()
defer b.stateAggregator.resumeStateUpdates()
return b.updateChildren(s, newConfig)
}
func (b *bal) ResolverError(err error) {
b.bg.ResolverError(err)
}
func (b *bal) UpdateSubConnState(sc balancer.SubConn, state balancer.SubConnState) {
b.logger.Errorf("UpdateSubConnState(%v, %+v) called unexpectedly", sc, state)
}
func (b *bal) Close() {
b.stateAggregator.close()
b.bg.Close()View on GitHub (pinned to 0c51461d27)
Solutions
- Always route config through clustermanager.ParseConfig(rawJSON) to produce *lbConfig.
- Ensure the resolver's service config produces the xds_cluster_manager_experimental policy with the expected JSON children/priorities shape.
- Check that the parent policy sends the exact *lbConfig returned by ParseConfig, not a wrapped or translated type.
Example fix
// before
b.UpdateClientConnState(balancer.ClientConnState{
BalancerConfig: &someOtherConfig{},
})
// after
import "google.golang.org/grpc/internal/xds/balancer/clustermanager"
cfg, err := clustermanager.ParseConfig(rawJSON)
if err != nil { return err }
b.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: cfg}) Defensive patterns
Strategy: type-guard
Validate before calling
func validateClusterManagerConfig(cfg serviceconfig.LoadBalancingConfig) error {
// *clustermanager.lbConfig is unexported; the public route is ParseConfig.
// Round-trip check: marshal then ParseConfig again.
raw, err := json.Marshal(cfg)
if err != nil { return err }
_, err = clustermanager.ParseConfig(raw)
return err
} Type guard
// lbConfig is unexported, so callers cannot name it. Use ParseConfig to obtain the only valid value. // In tests you can rely on the fact that bb.ParseConfig returns the only accepted type.
Prevention
- Always produce the config via clustermanager.ParseConfig rather than constructing it manually.
- Do not translate the config into a different type between resolver and balancer.
- Pin resolver and balancer packages to the same gRPC-go version.
When it happens
Trigger: UpdateClientConnState called on cluster_manager with a non-*lbConfig BalancerConfig; the xDS resolver produced the wrong shape or a custom parent bypassed clustermanager.ParseConfig.
Common situations: Custom resolver that doesn't use clustermanager.ParseConfig; gRPC version drift; tests that pass a raw struct.
Related errors
- unexpected balancer config with type: %T
- failed to JSON marshal load balancing policy for child %q: %
- failed to parse load balancing policy for child %q: %v
- failed to push new configuration %v to child %q: %v
- unexpected balancer config with type: %T
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/d7c69856b0b94a67.
Report an issue: GitHub.