grpc/grpc-go · error · balancer.ErrBadResolverState

pickfirst: received illegal BalancerConfig (type %T): %v: %w

Error message

pickfirst: received illegal BalancerConfig (type %T): %v: %w

What it means

Returned by pickfirst's UpdateClientConnState() (balancer/pickfirst/pickfirst.go:225). When state.BalancerConfig is non-nil but is not of type pfConfig, the type assertion at line 223 fails and this error is returned, wrapping balancer.ErrBadResolverState. It indicates the resolver or a parent LB policy delivered the wrong Go type as the pick_first config.

Source

Thrown at balancer/pickfirst/pickfirst.go:225

	})
}

func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.cancelConnectionTimer()
	if len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 {
		// Cleanup state pertaining to the previous resolver state.
		// Treat an empty address list like an error by calling b.ResolverError.
		b.closeSubConnsLocked()
		b.addressList.updateAddrs(nil)
		b.resolverErrorLocked(errors.New("produced zero addresses"))
		return balancer.ErrBadResolverState
	}
	b.healthCheckingEnabled = state.ResolverState.Attributes.Value(enableHealthListenerKeyType{}) != nil
	cfg, ok := state.BalancerConfig.(pfConfig)
	if state.BalancerConfig != nil && !ok {
		return fmt.Errorf("pickfirst: received illegal BalancerConfig (type %T): %v: %w", state.BalancerConfig, state.BalancerConfig, balancer.ErrBadResolverState)
	}

	if b.logger.V(2) {
		b.logger.Infof("Received new config %s, resolver state %s", pretty.ToJSON(cfg), pretty.ToJSON(state.ResolverState))
	}

	var newAddrs []resolver.Address
	if endpoints := state.ResolverState.Endpoints; len(endpoints) != 0 {
		// Perform the optional shuffling described in gRFC A62. The shuffling
		// will change the order of endpoints but not touch the order of the
		// addresses within each endpoint. - A61
		if cfg.ShuffleAddressList {
			if envconfig.PickFirstWeightedShuffling {
				type weightedEndpoint struct {
					endpoint resolver.Endpoint
					weight   float64
				}

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Ensure the resolver / parent policy that builds the pick_first config produces a pfConfig value (or nil for no config).
  2. If you wrote a custom balancer wrapping pick_first, pass nil BalancerConfig or a pfConfig.
  3. Check for grpc-go version mismatches between modules in a vendored/forked build.
  4. The error wraps ErrBadResolverState, so the channel will request re-resolution — fix the config source.

Example fix

// before: parent passes raw JSON / wrong type to pick_first child
cc.UpdateState(balancer.State{Picker: p})
child.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: someOtherConfig})

// after: pass a pfConfig (or nil)
child.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: pickfirst.PfConfig{}})
// or simply omit config by passing nil
Defensive patterns

Strategy: type-guard

Type guard

// Type guard: confirm the config is a pickfirst pfConfig before forwarding.
func isPFConfig(v any) bool {
    _, ok := v.(pickfirst.PfConfig)
    return ok
}
// Usage:
// if state.BalancerConfig != nil && !isPFConfig(state.BalancerConfig) { ... handle ... }

Prevention

When it happens

Trigger: UpdateClientConnState is called with a ClientConnState whose BalancerConfig is a non-nil value that is not a pfConfig (e.g. a json.RawMessage, a *iringhash.LBConfig, or some other policy's config struct). This is typically an internal wiring error in a parent balancer or a custom resolver, not a user-facing JSON typo.

Common situations: A custom resolver or parent LB policy forwards the wrong config type to a pick_first child; a balancer is registered under the pick_first name with a different config type; version skew between grpc-go and a fork/vendored copy that changed pfConfig.

Related errors


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