{"id":"6889259caa82f59b","repo":"go-redis/redis","slug":"failed-to-resolve-credentials-w","errorCode":null,"errorMessage":"failed to resolve credentials: %w","messagePattern":"failed to resolve credentials: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"redis.go","lineNumber":779,"sourceCode":"\t\t// the pre-fix wrappedOnClose approach — build an unbounded closure chain\n\t\t// retaining every prior connection's unsubscribe (see issue #3772).\n\t\t//\n\t\t// Note: pool.Conn.SetOnClose OVERWRITES any prior callback (see the\n\t\t// doc on that method). That is safe here because the streaming\n\t\t// credentials Manager deduplicates listeners by connection id, so a\n\t\t// second initConn on the same cn re-Subscribes the SAME listener and\n\t\t// the returned unsubscribe is equivalent to the one already installed.\n\t\t// Any future code path that could hand out a distinct unsubscribe on\n\t\t// re-initialization must first invoke the existing one to avoid\n\t\t// orphaning the old subscription on the credentials provider.\n\t\tcn.SetOnClose(unsubscribeFromCredentialsProvider)\n\n\t\tusername, password = credentials.BasicAuth()\n\t} else {\n\t\tusername, password, initErr = c.opt.resolveCredentials(ctx)\n\t\tif initErr != nil {\n\t\t\tcn.GetStateMachine().Transition(pool.StateClosed)\n\t\t\treturn fmt.Errorf(\"failed to resolve credentials: %w\", initErr)\n\t\t}\n\t}\n\n\t// for redis-server versions that do not support the HELLO command,\n\t// RESP2 will continue to be used.\n\t// helloOK tracks whether HELLO succeeded. If it did not, the connection\n\t// falls back to RESP2 regardless of c.opt.Protocol, and features that\n\t// require RESP3 (e.g. maintenance notifications) must be skipped.\n\thelloOK := false\n\t// For redis-server versions that do not support HELLO, RESP2 continues to\n\t// be used. Remember that negotiated fallback: configured Protocol remains 3,\n\t// but CSC must not serve without RESP3 invalidations.\n\thelloFallbackToRESP2 := false\n\tif initErr = conn.Hello(ctx, c.opt.Protocol, username, password, c.opt.ClientName).Err(); initErr == nil {\n\t\t// Authentication successful with HELLO command\n\t\thelloOK = true\n\t} else if !isRedisError(initErr) {\n\t\t// When the server responds with the RESP protocol and the result is not a normal","sourceCodeStart":761,"sourceCodeEnd":797,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/redis.go#L761-L797","documentation":"Thrown during per-connection initialization on the non-streaming path when Options.resolveCredentials(ctx) fails. This resolves credentials from context-based provider, function provider, or static Username/Password; failure means none of those sources could produce usable credentials, so the connection is closed and the error propagates.","triggerScenarios":"No StreamingCredentialsProvider set, but a CredentialsProvider (context), a CredentialsProviderFunc, or static Username/Password returned an error during dial. Occurs on the initial connection and on every reconnect since initConn re-runs resolution.","commonSituations":"Static password is empty/unset on a server requiring AUTH (NOAUTH); ACL user does not exist; CredentialsProviderFunc panics or returns an error; the context passed to credentials resolution was cancelled/expired; secret manager (Vault/AWS Secrets Manager) call failed.","solutions":["Confirm static Username/Password are correct and the ACL user exists on the server (redis-cli ACL GETUSER).","If using CredentialsProvider/CredentialsProviderFunc, ensure it returns (user, pass, nil) and does not depend on a cancelled context.","Test the secret source (Vault, SM, env var) independently to confirm it returns the credential.","Temporarily hardcode known-good credentials to isolate whether the failure is in resolution vs. the server."],"exampleFix":"// before\nopt.CredentialsProviderFunc = func(ctx context.Context) (string, string, error) {\n    return \"\", \"\", fmt.Errorf(\"vault unreachable\")\n}\n\n// after\nopt.CredentialsProviderFunc = func(ctx context.Context) (string, string, error) {\n    u, p, err := vault.GetRedisCreds(ctx)\n    if err != nil { return \"\", \"\", err }\n    return u, p, nil\n}","handlingStrategy":"validation","validationCode":"// Pre-flight the static or function-based credentials against the server.\nuser, pass, err := opt.resolveCredentials(ctx)\nif err != nil {\n    return fmt.Errorf(\"credentials resolve pre-check failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := client.Ping(ctx).Err(); err != nil {\n    if strings.Contains(err.Error(), \"failed to resolve credentials\") {\n        // re-resolve or surface to operator\n    }\n}","preventionTips":["Store Redis credentials in a secret manager and validate retrieval at startup.","Unit-test CredentialsProviderFunc with a fake context to ensure it never returns an error under normal load.","Monitor for NOAUTH-style errors as an early signal of credential drift."],"tags":["authentication","credentials","config"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}