go-redis/redis · error

redis: unexpected EXEC reply %q

Error message

redis: unexpected EXEC reply %q

What it means

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.

Source

Thrown at osscluster.go:2550

		cmd.SetErr(err)
		if firstFatal == nil {
			firstFatal = err
		}
	}

	// EXEC reply. ReadLine parses error lines into typed errors, so a non-nil
	// err means EXEC returned an error rather than the result array.
	c.txProcessPush(ctx, node, cn, rd)
	line, err := rd.ReadLine()
	if err != nil {
		if !isRedisError(err) {
			return c.txReadFatal(err) // IO error
		}
		return c.classifyExecError(err, firstRedirect, firstFatal)
	}

	if line[0] != proto.RespArray {
		err := fmt.Errorf("redis: unexpected EXEC reply %q", line)
		setCmdsErr(cmds, err)
		// A non-array aggregate reply may carry an unread payload.
		return &txOutcome{kind: txFatal, err: err, unreadReplies: true}
	}

	// Success: read the N command results.
	if err := node.Client.pipelineReadCmds(ctx, cn, rd, cmds); err != nil && !isRedisError(err) {
		return c.txReadFatal(err) // IO error mid-results
	}
	return &txOutcome{kind: txSuccess}
}

func (c *ClusterClient) txProcessPush(ctx context.Context, node *clusterNode, cn *pool.Conn, rd *proto.Reader) {
	if err := node.Client.processPendingPushNotificationWithReader(ctx, cn, rd); err != nil {
		internal.Logger.Printf(ctx, "push: error processing pending notifications before reading reply: %v", err)
	}
}

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Retry the transaction once on a fresh connection (the pool will discard the dirty one).
  2. Verify Protocol matches what the server/proxy supports (Protocol 3 vs 2).
  3. If a proxy is in front, ensure it forwards MULTI/EXEC replies unchanged; report persistent occurrences upstream.
Defensive patterns

Strategy: retry

Try / catch

if err := cmd.Err(); err != nil && strings.Contains(err.Error(), "unexpected EXEC reply") {
    // retry the MULTI/EXEC on a fresh connection; verify Protocol matches server/proxy
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/5b7c5633583f4819.json. Report an issue: GitHub.