{"id":"5b7c5633583f4819","repo":"go-redis/redis","slug":"redis-unexpected-exec-reply-q","errorCode":null,"errorMessage":"redis: unexpected EXEC reply %q","messagePattern":"redis: unexpected EXEC reply %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"osscluster.go","lineNumber":2550,"sourceCode":"\t\tcmd.SetErr(err)\n\t\tif firstFatal == nil {\n\t\t\tfirstFatal = err\n\t\t}\n\t}\n\n\t// EXEC reply. ReadLine parses error lines into typed errors, so a non-nil\n\t// err means EXEC returned an error rather than the result array.\n\tc.txProcessPush(ctx, node, cn, rd)\n\tline, err := rd.ReadLine()\n\tif err != nil {\n\t\tif !isRedisError(err) {\n\t\t\treturn c.txReadFatal(err) // IO error\n\t\t}\n\t\treturn c.classifyExecError(err, firstRedirect, firstFatal)\n\t}\n\n\tif line[0] != proto.RespArray {\n\t\terr := fmt.Errorf(\"redis: unexpected EXEC reply %q\", line)\n\t\tsetCmdsErr(cmds, err)\n\t\t// A non-array aggregate reply may carry an unread payload.\n\t\treturn &txOutcome{kind: txFatal, err: err, unreadReplies: true}\n\t}\n\n\t// Success: read the N command results.\n\tif err := node.Client.pipelineReadCmds(ctx, cn, rd, cmds); err != nil && !isRedisError(err) {\n\t\treturn c.txReadFatal(err) // IO error mid-results\n\t}\n\treturn &txOutcome{kind: txSuccess}\n}\n\nfunc (c *ClusterClient) txProcessPush(ctx context.Context, node *clusterNode, cn *pool.Conn, rd *proto.Reader) {\n\tif err := node.Client.processPendingPushNotificationWithReader(ctx, cn, rd); err != nil {\n\t\tinternal.Logger.Printf(ctx, \"push: error processing pending notifications before reading reply: %v\", err)\n\t}\n}\n","sourceCodeStart":2532,"sourceCodeEnd":2568,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/osscluster.go#L2532-L2568","documentation":"Raised by readTxPipelineReplies when the EXEC reply line for a cluster MULTI..EXEC transaction is not a RESP array (does not start with '*'). A successful EXEC must return an array of per-command results; any other aggregate type means the protocol stream is misaligned or the server sent something unexpected.","triggerScenarios":"A protocol-level mismatch during a cluster tx pipeline: a server that returns a non-array aggregate at EXEC (e.g. a nil bulk reply, an error the reader did not classify, or a truncated frame). All commands in the batch are marked with this error and the connection is flagged as having unread replies.","commonSituations":"RESP2/RESP3 mismatch, a proxy or fork that rewrites MULTI replies, a corrupted TCP stream, or an upstream bug. Usually accompanied by connection churn since the conn is considered dirty.","solutions":["Retry the transaction once on a fresh connection (the pool will discard the dirty one).","Verify Protocol matches what the server/proxy supports (Protocol 3 vs 2).","If a proxy is in front, ensure it forwards MULTI/EXEC replies unchanged; report persistent occurrences upstream."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := cmd.Err(); err != nil && strings.Contains(err.Error(), \"unexpected EXEC reply\") {\n    // retry the MULTI/EXEC on a fresh connection; verify Protocol matches server/proxy\n}","preventionTips":["Match Options.Protocol to what the server/proxy supports.","Ensure any proxy forwards MULTI/EXEC replies verbatim.","Set MaxRetries > 0 so transient protocol hiccups retry automatically."],"tags":["cluster","pipeline","transaction","protocol"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}