grpc/grpc-go · error
failed to start resolver: %w
Error message
failed to start resolver: %w
What it means
Inside exitIdleMode (clientconn.go:407-417), the channel calls resolverWrapper.start(). If starting the resolver fails, the channel is moved to TransientFailure with an erroring picker and the build error is wrapped as "failed to start resolver". Any subsequent RPC, or the Dial/DialContext that triggered exit, gets this error.
Source
Thrown at clientconn.go:416
cc.mu.Unlock()
// Set state to CONNECTING before building the name resolver
// so the channel does not remain in IDLE.
cc.csMgr.updateState(connectivity.Connecting)
// This needs to be called without cc.mu because this builds a new resolver
// which might update state or report error inline, which would then need to
// acquire cc.mu.
if err := cc.resolverWrapper.start(); err != nil {
// If resolver creation fails, treat it like an error reported by the
// resolver before any valid updates. Set channel's state to
// TransientFailure, and set an erroring picker with the resolver build
// error, which will returned as part of any subsequent RPCs.
logger.Warningf("Failed to start resolver: %v", err)
cc.csMgr.updateState(connectivity.TransientFailure)
cc.mu.Lock()
cc.updateResolverStateAndUnlock(resolver.State{}, err)
return fmt.Errorf("failed to start resolver: %w", err)
}
cc.addTraceEvent("exiting idle mode")
return nil
}
// initIdleStateLocked initializes common state to how it should be while idle.
func (cc *ClientConn) initIdleStateLocked() {
cc.resolverWrapper = newCCResolverWrapper(cc)
cc.balancerWrapper = newCCBalancerWrapper(cc)
cc.firstResolveEvent = grpcsync.NewEvent()
// cc.conns == nil is a proxy for the ClientConn being closed. So, instead
// of setting it to nil here, we recreate the map. This also means that we
// don't have to do this when exiting idle mode.
cc.conns = make(map[*addrConn]struct{})
}
// enterIdleMode puts the channel in idle mode, and as part of it shuts down theView on GitHub (pinned to 03255a9237)
Solutions
- Check the inner %w error for the resolver's Build failure message and fix that (register the builder, fix target scheme).
- Add the missing blank import for the resolver package (e.g. _ "google.golang.org/grpc/xds/googledirectpath") if it self-registers in init().
- Validate the target URL scheme before dialing.
Example fix
// before
conn, _ := grpc.NewClient("dns_nonexistent:///svc")
// after
import _ "google.golang.org/grpc/internal/resolver/dns"
conn, _ := grpc.NewClient("dns:///svc:443") Defensive patterns
Strategy: validation
Validate before calling
// Ensure a builder exists for the scheme (or default scheme) before dial.
func ensureResolver(scheme string) error {
for _, s := range resolver.GetSchemes() { if s == scheme { return nil } }
return fmt.Errorf("no resolver registered for scheme %q", scheme)
} Prevention
- Import resolver packages so their init() registers builders before main dials.
- Validate target scheme and default scheme at startup.
- In custom resolvers, make Build() return descriptive errors.
When it happens
Trigger: The resolver builder's Build() returns an error when the channel leaves idle mode — e.g. a custom resolver failing, or no builder registered for the target's scheme. Triggered on first RPC (NewClient) or immediately (Dial/DialContext).
Common situations: Target uses a scheme with no registered resolver; a custom resolver builder panics/errors; the resolver plugin isn't imported (blank import missing) so its init() never registers.
Related errors
- failed to exit idle mode: %w
- could not get resolver for default scheme: %q
- dns resolver: missing address
- dns resolver: missing port after port-separator colon
- passthrough: received empty target in Build()
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/4fb537e07a908bc5.
Report an issue: GitHub.