{"record":{"id":"0c60fb4223f018f1","repo":"grpc/grpc-go","slug":"failed-to-exit-idle-mode-w","errorCode":null,"errorMessage":"failed to exit idle mode: %w","messagePattern":"failed to exit idle mode: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clientconn.go","lineNumber":303,"sourceCode":"\t// resolution on the client.\n\topts = append([]DialOption{withDefaultScheme(\"passthrough\"), WithLocalDNSResolution()}, opts...)\n\tcc, err := NewClient(target, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// We start the channel off in idle mode, but kick it out of idle now,\n\t// instead of waiting for the first RPC.  This is the legacy behavior of\n\t// Dial.\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tcc.Close()\n\t\t}\n\t}()\n\n\t// This creates the name resolver, load balancer, etc.\n\tif err := cc.exitIdleMode(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to exit idle mode: %w\", err)\n\t}\n\tcc.idlenessMgr.UnsafeSetNotIdle()\n\n\t// Return now for non-blocking dials.\n\tif !cc.dopts.block {\n\t\treturn cc, nil\n\t}\n\n\tif cc.dopts.timeout > 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, cc.dopts.timeout)\n\t\tdefer cancel()\n\t}\n\tdefer func() {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tswitch {\n\t\t\tcase ctx.Err() == err:","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/clientconn.go#L285-L321","documentation":"DialContext (the deprecated Dial family) calls NewClient then immediately cc.exitIdleMode() to kick the channel out of idle (clientconn.go:302). exitIdleMode starts the resolver wrapper; if that fails, the error is wrapped as \"failed to exit idle mode\". The underlying cause is almost always a resolver build failure.","triggerScenarios":"Using grpc.Dial / DialContext with a target whose scheme's resolver cannot be built at start time, or when the channel is concurrently closed. The inner error (\"failed to start resolver\") holds the real cause.","commonSituations":"An unregistered or misspelled scheme in the dial target; a custom resolver builder that errors in Build(); calling Close() on the channel from another goroutine during Dial.","solutions":["Inspect the wrapped error (%w) for the inner cause — usually \"failed to start resolver\" — and fix that.","Verify the dial target scheme matches a registered resolver (e.g. dns, passthrough) or migrate to grpc.NewClient.","Avoid concurrent Close() during Dial; ensure the resolver builder is registered before dialing."],"exampleFix":"// before\nconn, err := grpc.Dial(\"xds:///nonexistent\", grpc.WithBlock())\n// after\nconn, err := grpc.NewClient(\"dns:///my-service.example:443\")","handlingStrategy":"try-catch","validationCode":"// Verify the target's scheme resolves to a registered builder before dial.\nfunc schemeRegistered(target string) bool {\n    u, err := url.Parse(target)\n    if err != nil { return false }\n    if u.Scheme == \"\" { return true } // default scheme will be applied\n    for _, name := range resolver.GetSchemes() { if name == u.Scheme { return true } }\n    return false\n}","typeGuard":null,"tryCatchPattern":"conn, err := grpc.DialContext(ctx, target, grpc.WithBlock())\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to exit idle mode\") {\n        // unwrap to find the resolver cause; do not retry blindly\n        log.Printf(\"dial failed at resolver startup: %v\", err)\n    }\n    return nil, err\n}","preventionTips":["Prefer grpc.NewClient over the deprecated Dial/DialContext to avoid the eager exit-idle step.","Ensure resolver builders are registered (blank imports) before dialing.","Don't call Close() concurrently with Dial."],"tags":["go","grpc","clientconn","resolver","dial"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}