{"record":{"id":"62bd829b1c88698a","repo":"grpc/grpc-go","slug":"pickfirst-received-illegal-balancerconfig-type","errorCode":null,"errorMessage":"pickfirst: received illegal BalancerConfig (type %T): %v: %w","messagePattern":"pickfirst: received illegal BalancerConfig \\(type %T\\): (.+?): %w","errorType":"validation","errorClass":"balancer.ErrBadResolverState","httpStatus":null,"severity":"error","filePath":"balancer/pickfirst/pickfirst.go","lineNumber":225,"sourceCode":"\t})\n}\n\nfunc (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {\n\tb.mu.Lock()\n\tdefer b.mu.Unlock()\n\tb.cancelConnectionTimer()\n\tif len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 {\n\t\t// Cleanup state pertaining to the previous resolver state.\n\t\t// Treat an empty address list like an error by calling b.ResolverError.\n\t\tb.closeSubConnsLocked()\n\t\tb.addressList.updateAddrs(nil)\n\t\tb.resolverErrorLocked(errors.New(\"produced zero addresses\"))\n\t\treturn balancer.ErrBadResolverState\n\t}\n\tb.healthCheckingEnabled = state.ResolverState.Attributes.Value(enableHealthListenerKeyType{}) != nil\n\tcfg, ok := state.BalancerConfig.(pfConfig)\n\tif state.BalancerConfig != nil && !ok {\n\t\treturn fmt.Errorf(\"pickfirst: received illegal BalancerConfig (type %T): %v: %w\", state.BalancerConfig, state.BalancerConfig, balancer.ErrBadResolverState)\n\t}\n\n\tif b.logger.V(2) {\n\t\tb.logger.Infof(\"Received new config %s, resolver state %s\", pretty.ToJSON(cfg), pretty.ToJSON(state.ResolverState))\n\t}\n\n\tvar newAddrs []resolver.Address\n\tif endpoints := state.ResolverState.Endpoints; len(endpoints) != 0 {\n\t\t// Perform the optional shuffling described in gRFC A62. The shuffling\n\t\t// will change the order of endpoints but not touch the order of the\n\t\t// addresses within each endpoint. - A61\n\t\tif cfg.ShuffleAddressList {\n\t\t\tif envconfig.PickFirstWeightedShuffling {\n\t\t\t\ttype weightedEndpoint struct {\n\t\t\t\t\tendpoint resolver.Endpoint\n\t\t\t\t\tweight   float64\n\t\t\t\t}\n","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/balancer/pickfirst/pickfirst.go#L207-L243","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the resolver / parent policy that builds the pick_first config produces a pfConfig value (or nil for no config).","If you wrote a custom balancer wrapping pick_first, pass nil BalancerConfig or a pfConfig.","Check for grpc-go version mismatches between modules in a vendored/forked build.","The error wraps ErrBadResolverState, so the channel will request re-resolution — fix the config source."],"exampleFix":"// before: parent passes raw JSON / wrong type to pick_first child\ncc.UpdateState(balancer.State{Picker: p})\nchild.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: someOtherConfig})\n\n// after: pass a pfConfig (or nil)\nchild.UpdateClientConnState(balancer.ClientConnState{BalancerConfig: pickfirst.PfConfig{}})\n// or simply omit config by passing nil","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Type guard: confirm the config is a pickfirst pfConfig before forwarding.\nfunc isPFConfig(v any) bool {\n    _, ok := v.(pickfirst.PfConfig)\n    return ok\n}\n// Usage:\n// if state.BalancerConfig != nil && !isPFConfig(state.BalancerConfig) { ... handle ... }","tryCatchPattern":null,"preventionTips":["When wrapping pick_first in a parent balancer, always pass nil or a pfConfig as BalancerConfig.","Do not forward raw json.RawMessage or other policies' config types to a pick_first child.","Pin grpc-go versions across modules to avoid pfConfig type drift."],"tags":["grpc","balancer","pickfirst","config","type-assertion","internal"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}