grpc/grpc-go · error
unexpected balancer config with type: %T
Error message
unexpected balancer config with type: %T
What it means
priority's UpdateClientConnState asserts s.BalancerConfig is *priority.LBConfig (balancer.go:121-123). Any other type is rejected because priority needs the Children map and ordered Priorities list to manage its sub-balancers.
Source
Thrown at internal/xds/balancer/priority/balancer.go:123
priorities []string
// children is a map from child name to sub-balancers.
children map[string]*childBalancer
// Set during UpdateClientConnState when calling into sub-balancers.
// Prevents child updates from recomputing the active priority or sending
// an update of the aggregated picker to the parent. Cleared after all
// sub-balancers have finished UpdateClientConnState, after which
// syncPriority is called manually.
inhibitPickerUpdates bool
}
func (b *priorityBalancer) UpdateClientConnState(s balancer.ClientConnState) error {
if b.logger.V(2) {
b.logger.Infof("Received an update with 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)
}
endpointsSplit := hierarchy.Group(s.ResolverState.Endpoints)
b.mu.Lock()
// Create and remove children, since we know all children from the config
// are used by some priority.
for name, newSubConfig := range newConfig.Children {
bb := balancer.Get(newSubConfig.Config.Name)
if bb == nil {
b.logger.Errorf("balancer name %v from config is not registered", newSubConfig.Config.Name)
continue
}
currentChild, ok := b.children[name]
if !ok {
// This is a new child, add it to the children list. But note that
// the balancer isn't built, because this child can be a low
// priority. If necessary, it will be built when syncing priorities.View on GitHub (pinned to 0c51461d27)
Solutions
- Always route config through priority.ParseConfig(rawJSON) to produce *LBConfig.
- Ensure the resolver's service config produces the priority_experimental policy with the expected Children/Priorities JSON shape.
- Verify the parent policy forwards the exact *LBConfig returned by ParseConfig.
Example fix
// before
b.UpdateClientConnState(balancer.ClientConnState{
BalancerConfig: &someOtherConfig{},
})
// after
import "google.golang.org/grpc/internal/xds/balancer/priority"
cfg, err := priority.ParseConfig(rawJSON)
if err != nil { return err }
b.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: cfg}) Defensive patterns
Strategy: type-guard
Validate before calling
func validatePriorityConfig(cfg serviceconfig.LoadBalancingConfig) error {
if _, ok := cfg.(*priority.LBConfig); !ok {
return fmt.Errorf("expected *priority.LBConfig, got %T", cfg)
}
return nil
} Type guard
func isPriorityConfig(cfg serviceconfig.LoadBalancingConfig) bool {
_, ok := cfg.(*priority.LBConfig)
return ok
} Prevention
- Always produce the config via priority.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 the priority balancer with a BalancerConfig that is not *priority.LBConfig; usually means the xDS resolver produced the wrong config or a custom parent bypassed ParseConfig.
Common situations: Custom resolver that does not use priority.ParseConfig; gRPC version drift; tests that pass a raw struct.
Related errors
- unexpected balancer config with type: %T
- unexpected balancer config with type: %T
- LB policy name %q found in Priorities field (%v) is not foun
- LB policy name %q found in Children field (%v) is not found
- last resolver error: %v
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/253fa02406ef5817.
Report an issue: GitHub.