grpc/grpc-go · error

name resolver error

Error message

name resolver error: %v

What it means

Produced by pickfirst's resolverErrorLocked() (balancer/pickfirst/pickfirst.go:206). The picker wraps this error when the balancer transitions to TransientFailure because the name resolver produced an error and there is no usable previous address list to fall back on (addressList.size() == 0 or state is already TransientFailure). It means name resolution failed and the channel has no addresses to try.

Solutions

  1. Inspect the embedded resolver error (%v) — it is the concrete cause (DNS error, xDS error, etc.).
  2. Verify the target URI / service name is correct and resolvable.
  3. If using DNS, confirm the DNS server is reachable and returns records.
  4. If using xDS, verify the EDS/CDS resources are present and non-empty.

Example fix

// before: unresolvable target name
conn, _ := grpc.NewClient("dns:///nonexistent-host:50051", grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"pick_first":{}}]}`))

// after: valid resolvable target
conn, _ := grpc.NewClient("dns:///real-svc.default.svc.cluster.local:50051", grpc.WithDefaultServiceConfig(`{"loadBalancingConfig":[{"pick_first":{}}]}`))
Defensive patterns

Strategy: retry

Try / catch

// Name resolver error → Unavailable. Retry; the channel re-resolves.
for {
    resp, err := c.Call(ctx, in, grpc.WaitForReady(true))
    if err == nil { return resp, nil }
    if status.Code(err) != codes.Unavailable { return nil, err }
    select {
    case <-time.After(backoff):
    case <-ctx.Done(): return nil, ctx.Err()
    }
}

Prevention

When it happens

Trigger: The resolver reports an error via ResolverError(), and either the balancer never received a good update (addressList.size() == 0, line 197) or the balancer is already in TransientFailure. The picker is set at line 204-207 to report 'name resolver error: <err>'. This also fires when UpdateClientConnState receives zero addresses (line 219) which calls resolverErrorLocked.

Common situations: DNS resolution fails (NXDOMAIN, DNS server unreachable); the target URI is malformed; an xDS resolver returns no endpoints and no prior good list exists; the service name is wrong; the resolver implementation throws.

Related errors


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

Appendix: source

Thrown at balancer/pickfirst/pickfirst.go:206

func (b *pickfirstBalancer) resolverErrorLocked(err error) {
	if b.logger.V(2) {
		b.logger.Infof("Received error from the name resolver: %v", err)
	}

	// The picker will not change since the balancer does not currently
	// report an error. If the balancer hasn't received a single good resolver
	// update yet, transition to TRANSIENT_FAILURE.
	if b.state != connectivity.TransientFailure && b.addressList.size() > 0 {
		if b.logger.V(2) {
			b.logger.Infof("Ignoring resolver error because balancer is using a previous good update.")
		}
		return
	}

	b.updateBalancerState(balancer.State{
		ConnectivityState: connectivity.TransientFailure,
		Picker:            &picker{err: fmt.Errorf("name resolver error: %v", err)},
	})
}

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 {

View on GitHub (pinned to 0c51461d27)