Repository: redis/lettuce (java)

Issue:
io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required on CLIENT and READONLY command
## Bug Report

<!-- First of all: Have you checked the docs https://github.com/lettuce-io/lettuce-core/wiki, GitHub issues, or Stack Overflow whether someone else has already reported your issue? -->

#### Current Behavior

<!-- A clear and concise description of the behavior.-->
<!-- See https://stackoverflow.com/help/minimal-reproducible-example for details of what a good minimum reproducible example is -->
When attempting to connect to an AWS MemoryDB cluster using Lettuce version 6.4.0.RELEASE, an error occurs related to authentication for the CLIENT and READONLY commands. This issue persists regardless of the protocol version used (RESP2 or RESP3). However, requests to the MemoryDB cluster still succeed and return responses despite this error.

```
2024-09-28 01:00:32.236 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xbefb39e8, /100.69.22.144:35512 -> memorydb-finance-0011-001.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.135:6379, epid=0x75c, chid=0x761] Completing command AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]

2024-09-28 02:07:12.067 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-9] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xb411c837, /100.69.84.97:51846 -> memorydb-finance-0003-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.232.43:6379, epid=0x37b, chid=0x183d9] Completing command AsyncCommand [type=READONLY, output=StatusOutput [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]

```

<details>
<summary>Stack trace</summary>

```java
// your stack trace here;
// Following stack trace occurred once the client connected to the cluster
2024-09-28 00:44:09.819 yrid= x-request-id= uri=/v8/finance/spark?symbols=VZ method=GET [qtp1032000752-103] DEBUG io.lettuce.core.AbstractRedisClient - Connecting to Redis at clustercfg
.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com:6379
2024-09-28 00:44:09.922 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, [id: 0xed1622ae] (inactive), e
pid=0x1, chid=0x1] channelRegistered()
2024-09-28 00:44:09.960 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] write(ctx, [AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='null
'], commandType=io.lettuce.core.protocol.Command], AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command]], promise)
2024-09-28 00:44:09.962 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379] writing command AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='null'], commandType=
io.lettuce.core.protocol.Command]
2024-09-28 00:44:09.962 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379] writing command AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='null'], commandType=
io.lettuce.core.protocol.Command]
2024-09-28 00:44:09.965 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] write(ctx, AsyncCommand [type=HELLO, output=GenericMapOutput [output=null, error='nu
ll'], commandType=io.lettuce.core.protocol.Command], promise)
2024-09-28 00:44:09.965 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandEncoder - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379] writing command AsyncCommand [type=HELLO, output=GenericMapOutput [output=null, error='null'], commandTy
pe=io.lettuce.core.protocol.Command]
2024-09-28 00:44:09.982 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Received: 214 bytes, 3 commands in the stack
2024-09-28 00:44:09.983 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Stack contains: 3 commands
2024-09-28 00:44:09.984 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode done, empty stack: true
2024-09-28 00:44:09.984 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> clustercfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Completing command AsyncCommand [type=CLIENT, output=StatusOutput [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]
024-09-28 00:44:09.986 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Stack contains: 2 commands
2024-09-28 00:44:09.986 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode done, empty stack: true
2024-09-28 00:44:09.986 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> cluster
cfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Completing command AsyncCommand [type=CLIENT, output=StatusOutput [output=null, erro
r='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]
2024-09-28 00:44:09.988 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.RedisHandshake - Error applying connection metadata
java.util.concurrent.CompletionException: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
        at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
        at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
        at java.util.concurrent.CompletableFuture.biRelay(CompletableFuture.java:1298)
        at java.util.concurrent.CompletableFuture$BiRelay.tryFire(CompletableFuture.java:1284)
        at java.util.concurrent.CompletableFuture$CoCompletion.tryFire(CompletableFuture.java:1034)
        at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
        at java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:1990)
        at io.lettuce.core.protocol.AsyncCommand.doCompleteExceptionally(AsyncCommand.java:143)
        at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:124)
        at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:115)
        at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:749)
        at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:684)
        at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:601)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
           at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1486)
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1235)
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1282)
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:437)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.lang.Thread.run(Thread.java:750)
Caused by: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:120)
        ... 28 common frames omitted
2024-09-28 00:44:09.988 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-1] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x47827c96, /100.69.84.97:51222 -> clustercfg.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.226.99:6379, epid=0x1, chid=0x1] Stack contains: 1 commands


// Following occurred after app deployed for about an hour
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.ConnectionWatchdog - [channel=0xc315e35b, /100.69.84.97:54240 -> mem
orydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, last known addr=memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.
amazonaws.com/10.157.193.94:6379] scheduleReconnect()
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.ConnectionWatchdog - [channel=0xc315e35b, /100.69.84.97:54240 -> mem
orydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, last known addr=memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.
amazonaws.com/10.157.193.94:6379] Reconnect attempt 318, delay 2991ms
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xbec8b645, /100.69.84.97:53084 -> memoryd
b-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, epid=0x29b, chid=0x183bf] channelInactive()
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc315e35b, /100.69.84.97:54240 -> memory
db-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, epid=0x29b] deactivating endpoint handler
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.DefaultEndpoint - [channel=0xc315e35b, /100.69.84.97:54240 -> memory
db-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, epid=0x29b] notifyQueuedCommands adding 0 command(s) to buffer
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xbec8b645, /100.69.84.97:53084 -> memoryd
b-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, epid=0x29b, chid=0x183bf] channelInactive() done
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.ConnectionWatchdog - [channel=0xc315e35b, /100.69.84.97:54240 -> mem
orydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, last known addr=memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.
amazonaws.com/10.157.193.94:6379] channelInactive()
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.ConnectionWatchdog - [channel=0xc315e35b, /100.69.84.97:54240 -> memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, last known addr=memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379] scheduleReconnect()
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.ConnectionWatchdog - [channel=0xc315e35b, /100.69.84.97:54240 -> memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, last known addr=memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379] Skipping scheduleReconnect() because I have an active channel
2024-09-28 02:07:11.668 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-6] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0xbec8b645, /100.69.84.97:53084 -> memorydb-finance-0006-003.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.193.94:6379, epid=0x29b, chid=0x183bf] channelUnregistered()
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memorydb-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Received: 249 bytes, 4 commands in the stack
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
b-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Completing command AsyncCommand [type=READONLY, output=StatusOu
tput [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
b-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Stack contains: 3 commands
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode done, empty stack: true
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
b-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Completing command AsyncCommand [type=CLIENT, output=StatusOutp
ut [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
b-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Stack contains: 2 commands
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode done, empty stack: true
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memoryd
b-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Completing command AsyncCommand [type=CLIENT, output=StatusOutp
ut [output=null, error='NOAUTH Authentication required.'], commandType=io.lettuce.core.protocol.Command]
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.RedisHandshake - Error applying connection metadata
java.util.concurrent.CompletionException: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
        at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
        at java.util.concurrent.CompletableFuture.biRelay(CompletableFuture.java:1298)
        at java.util.concurrent.CompletableFuture$BiRelay.tryFire(CompletableFuture.java:1284)
        at java.util.concurrent.CompletableFuture$CoCompletion.tryFire(CompletableFuture.java:1034)
        at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
        at java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:1990)
        at io.lettuce.core.protocol.AsyncCommand.doCompleteExceptionally(AsyncCommand.java:143)
        at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:124)
        at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:115)
        at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:749)
        at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:684)
        at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:601)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1486)
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1235)
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1282)
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:498)
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:437)
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.lang.Thread.run(Thread.java:750)
Caused by: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
        at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:120)
        ... 28 common frames omitted
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memorydb-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Stack contains: 1 commands
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.RedisStateMachine - Decode done, empty stack: true
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] DEBUG io.lettuce.core.protocol.CommandHandler - [channel=0x9fe575db, /100.69.84.97:56996 -> memorydb-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com/10.157.198.169:6379, epid=0x33, chid=0x183c1] Completing command AsyncCommand [type=HELLO, output=GenericMapOutput [output={server=redis, version=7.1.1, proto=3, id=2719043, mode=cluster, role=replica}, error='null'], commandType=io.lettuce.core.protocol.Command]
2024-09-28 02:07:11.669 yrid= x-request-id= uri= method= [lettuce-epollEventLoop-4-4] WARN  io.lettuce.core.protocol.ConnectionWatchdog - Cannot reconnect to [memorydb-finance-0010-002.memorydb-finance.zek3dz.memorydb.us-east-1.amazonaws.com:6379]: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
java.util.concurrent.CompletionException: io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required.
        at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
        at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)

```

</details>

#### Input Code

<!-- Java/Kotlin/Scala/Groovy/… or Repo link if applicable: -->

<details>
<summary>Input Code</summary>

```java
// your code here;
// How we initialize the client
@Override
    public RedisClusterClient get() {
        LOGGER.info("Initializing RedisClusterClient");
        RedisURI redisURI = RedisURI.Builder.redis(hostName)
                .withPort(6379)
                .withAuthentication(redisCredentialProvider)
                .withSsl(true)
                .build();
        
        ClientResources resources = DefaultClientResources.builder()
                .reconnectDelay(Delay.fullJitter(
                        Duration.ofMillis(100),     // minimum 100 millisecond delay
                        Duration.ofSeconds(5),      // maximum 5 second delay
                        100, TimeUnit.MILLISECONDS) // 100 millisecond base
                ).build();

        RedisClusterClient client = RedisClusterClient.create(resources, redisURI);
        client.setOptions(
                ClusterClientOptions.builder()
                        .autoReconnect(true)
                        .socketOptions(SocketOptions.builder()
                                .connectTimeout(Duration.ofMillis(connectTimeoutMillis))
                                .keepAlive(true)
                                .build()
                        )
                        .timeoutOptions(TimeoutOptions.builder()
                                .fixedTimeout(Duration.ofMillis(commandTimeoutMillis))
                                .build()
                        )
                        .topologyRefreshOptions(ClusterTopologyRefreshOptions.builder()
                                .enablePeriodicRefresh(Duration.ofSeconds(10))
                                .enableAllAdaptiveRefreshTriggers()
                                .dynamicRefreshSources(true)
                                .build()
                        )
                        // https://redis.io/docs/latest/commands/cluster-nodes/
                        .nodeFilter(it ->
                                !(it.is(RedisClusterNode.NodeFlag.FAIL)
                                        || it.is(RedisClusterNode.NodeFlag.EVENTUAL_FAIL)
                                        || it.is(RedisClusterNode.NodeFlag.HANDSHAKE)
                                        || it.is(RedisClusterNode.NodeFlag.NOADDR)
                                        || (it.is(RedisClusterNode.NodeFlag.REPLICA) && it.getReplOffset() == -1L)))
                        .build());

        Runtime.getRuntime().addShutdownHook(new Thread(client::shutdown));
        return client;
    }


// How we fetch the credentials in RedisStaticCredentialsProvider 
  
 private volatile RedisCredentials cachedCredentials = null;


 @Override
    public Mono<RedisCredentials> resolveCredentials() {
        // Check if credentials are already fetched
        if (cachedCredentials != null) {
            return Mono.just(cachedCredentials);
        }
        fetchCredentials();
        return Mono.just(cachedCredentials);
    }

 private void fetchCredentials() {
        LOGGER.info("Resolving Credentials");
        // Check if credentials are already fetched
        if (cachedCredentials != null) {
            return;
        }
        synchronized (this) {
            if (cachedCredentials == null) { // Double-checked locking
                if (this.yKeyKeyClient != null) {
                    // ...
                    if (usernameSecret != null && passwordSecret != null) {
                        LOGGER.info("Get credentials successfully, duration={}", System.currentTimeMillis() - startTime);
                        cachedCredentials = RedisCredentials.just(usernameSecret, passwordSecret);
                    } else {
                        LOGGER.error("Null username or password secrets returned");
                    }
                } else {
                    LOGGER.error("YKeyKeyClient is not successfully initialized");
                }
            }
        }
    }
```

</details>

#### Expected behavior/code

<!-- A clear and concise description of what you expected to happen (or code). -->
The CLIENT and READONLY commands should execute successfully without authentication errors.


#### Environment

- Lettuce version(s): [6.4.0.RELEASE]
- Redis version: [7.1]

#### Possible Solution

<!-- Only if you have suggestions on a fix for the bug -->

#### Additional context

<!-- Add any other context about the problem here. Do not add code as screenshots. -->


Pull request description:
io.lettuce.core.RedisCommandExecutionException: NOAUTH Authentication required on CLIENT and READONLY command
Using custom credentials provider can delay providing credentials. In this case **applyPostHandshake** and **applyConnectionMetadata** got executed before **handshake** and led to a NOAUTH error in the log for CLIENT command.
Closes #2996
<!--
Thank you for proposing a pull request. This template will guide you through the essential steps necessary for a pull request.
-->
Make sure that:

- [x] You have read the [contribution guidelines](https://github.com/lettuce-io/lettuce-core/blob/main/.github/CONTRIBUTING.md).
- [x] You have created a feature request first to discuss your contribution intent. Please reference the feature request ticket number in the pull request.
- [x] You applied code formatting rules using the `mvn formatter:format` target. Don’t submit any formatting related changes.
- [x] You submit test cases (unit or integration tests) that back your changes.

 <!--
Great! Live long and prosper.
-->


Patch:
diff --git a/pom.xml b/pom.xml
index da7a50f5c..ddfc4877b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,7 @@
     </developers>
 
     <properties>
+        <awaitility.version>4.2.2</awaitility.version>
         <assertj-core.version>3.25.3</assertj-core.version>
         <cdi-api.version>2.0.SP1</cdi-api.version>
         <brave.version>5.13.11</brave.version>
@@ -103,7 +104,12 @@
 
     <dependencyManagement>
         <dependencies>
-
+            <dependency>
+                <groupId>org.awaitility</groupId>
+                <artifactId>awaitility</artifactId>
+                <version>${awaitility.version}</version>
+                <scope>test</scope>
+            </dependency>
             <dependency>
                 <groupId>io.netty</groupId>
                 <artifactId>netty-bom</artifactId>
@@ -327,6 +333,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.awaitility</groupId>
+            <artifactId>awaitility</artifactId>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.hdrhistogram</groupId>
             <artifactId>HdrHistogram</artifactId>
diff --git a/src/main/java/io/lettuce/core/RedisHandshake.java b/src/main/java/io/lettuce/core/RedisHandshake.java
index 21fe829bb..80dc62096 100644
--- a/src/main/java/io/lettuce/core/RedisHandshake.java
+++ b/src/main/java/io/lettuce/core/RedisHandshake.java
@@ -101,21 +101,14 @@ class RedisHandshake implements ConnectionInitializer {
                     new RedisConnectionException("Protocol version" + this.requestedProtocolVersion + " not supported"));
         }
 
-        // post-handshake commands, whose execution failures would cause the connection to be considered
-        // unsuccessfully established
-        CompletableFuture<Void> postHandshake = applyPostHandshake(channel);
-
-        // post-handshake commands, executed in a 'fire and forget' manner, to avoid having to react to different
-        // implementations or versions of the server runtime, and whose execution result (whether a success or a
-        // failure ) should not alter the outcome of the connection attempt
-        CompletableFuture<Void> connectionMetadata = applyConnectionMetadata(channel).handle((result, error) -> {
-            if (error != null) {
-                LOG.debug("Error applying connection metadata", error);
-            }
-            return null;
-        });
-
-        return handshake.thenCompose(ignore -> postHandshake).thenCompose(ignore -> connectionMetadata);
+        return handshake
+                // post-handshake commands, whose execution failures would cause the connection to be considered
+                // unsuccessfully established
+                .thenCompose(ignore -> applyPostHandshake(channel))
+                // post-handshake commands, executed in a 'fire and forget' manner, to avoid having to react to different
+                // implementations or versions of the server runtime, and whose execution result (whether a success or a
+                // failure ) should not alter the outcome of the connection attempt
+                .thenCompose(ignore -> applyConnectionMetadataSafely(channel));
     }
 
     private CompletionStage<?> tryHandshakeResp3(Channel channel) {
@@ -271,6 +264,15 @@ class RedisHandshake implements ConnectionInitializer {
         return dispatch(channel, postHandshake);
     }
 
+    private CompletionStage<Void> applyConnectionMetadataSafely(Channel channel) {
+        return applyConnectionMetadata(channel).handle((result, error) -> {
+            if (error != null) {
+                LOG.debug("Error applying connection metadata", error);
+            }
+            return null;
+        });
+    }
+
     private CompletableFuture<Void> applyConnectionMetadata(Channel channel) {
 
         List<AsyncCommand<?, ?, ?>> postHandshake = new ArrayList<>();


Test patch:
diff --git a/src/test/java/io/lettuce/core/RedisHandshakeUnitTests.java b/src/test/java/io/lettuce/core/RedisHandshakeUnitTests.java
index d4b968c4f..b01fd810f 100644
--- a/src/test/java/io/lettuce/core/RedisHandshakeUnitTests.java
+++ b/src/test/java/io/lettuce/core/RedisHandshakeUnitTests.java
@@ -1,6 +1,7 @@
 package io.lettuce.core;
 
 import static io.lettuce.TestTags.UNIT_TEST;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.assertj.core.api.Assertions.*;
 
 import java.nio.ByteBuffer;
@@ -8,6 +9,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletionStage;
 
+import org.awaitility.Awaitility;
 import org.junit.jupiter.api.Tag;
 import org.junit.jupiter.api.Test;
 
@@ -15,6 +17,8 @@ import io.lettuce.core.output.CommandOutput;
 import io.lettuce.core.protocol.AsyncCommand;
 import io.lettuce.core.protocol.ProtocolVersion;
 import io.netty.channel.embedded.EmbeddedChannel;
+import reactor.core.publisher.Mono;
+import reactor.core.publisher.Sinks;
 
 /**
  * Unit tests for {@link RedisHandshake}.
@@ -106,6 +110,42 @@ class RedisHandshakeUnitTests {
         assertThat(handshakeInit.toCompletableFuture().isCompletedExceptionally()).isFalse();
     }
 
+    @Test
+    void handshakeDelayedCredentialProvider() {
+
+        DelayedRedisCredentialsProvider cp = new DelayedRedisCredentialsProvider();
+        // RedisCredentialsProvider cp = () -> Mono.just(RedisCredentials.just("foo",
+        // "bar")).delayElement(Duration.ofMillis(3));
+        EmbeddedChannel channel = new EmbeddedChannel(true, false);
+
+        ConnectionMetadata connectionMetdata = new ConnectionMetadata();
+        connectionMetdata.setLibraryName("library-name");
+        connectionMetdata.setLibraryVersion("library-version");
+
+        ConnectionState state = new ConnectionState();
+        state.setCredentialsProvider(cp);
+        state.apply(connectionMetdata);
+        RedisHandshake handshake = new RedisHandshake(null, false, state);
+        CompletionStage<Void> handshakeInit = handshake.initialize(channel);
+        cp.completeCredentials(RedisCredentials.just("foo", "bar"));
+
+        Awaitility.await().atMost(50, MILLISECONDS) // Wait up to 5 seconds
+                .pollInterval(5, MILLISECONDS) // Poll every 50 milliseconds
+                .until(() -> !channel.outboundMessages().isEmpty());
+
+        AsyncCommand<String, String, Map<String, String>> hello = channel.readOutbound();
+        helloResponse(hello.getOutput());
+        hello.complete();
+
+        List<AsyncCommand<String, String, Map<String, String>>> postHandshake = channel.readOutbound();
+        postHandshake.get(0).getOutput().setError(ERR_UNKNOWN_COMMAND);
+        postHandshake.get(0).completeExceptionally(new RedisException(ERR_UNKNOWN_COMMAND));
+        postHandshake.get(0).complete();
+
+        assertThat(postHandshake.size()).isEqualTo(2);
+        assertThat(handshakeInit.toCompletableFuture().isCompletedExceptionally()).isFalse();
+    }
+
     @Test
     void shouldParseVersionWithCharacters() {
 
@@ -136,4 +176,19 @@ class RedisHandshakeUnitTests {
         output.set(ByteBuffer.wrap("1.2.3".getBytes()));
     }
 
+    static class DelayedRedisCredentialsProvider implements RedisCredentialsProvider {
+
+        private final Sinks.One<RedisCredentials> credentialsSink = Sinks.one();
+
+        @Override
+        public Mono<RedisCredentials> resolveCredentials() {
+            return credentialsSink.asMono();
+        }
+
+        public void completeCredentials(RedisCredentials credentials) {
+            credentialsSink.tryEmitValue(credentials);
+        }
+
+    }
+
 }