go-redis/redis · error
redis: expected '*', but got line %q
Error message
redis: expected '*', but got line %q
What it means
Thrown by txPipelineReadQueued after MULTI sends QUEUED acknowledgements for every command: the next line from the server must be a RESP array ('*<n>') giving the number of replies. If the first byte is not '*' (e.g. an inline error, a push frame leaking through, or protocol desync), the pipeline transaction is considered corrupt and the error is returned.
Source
Thrown at redis.go:1889
}
}
}
// To be sure there are no buffered push notifications, we process them before reading the reply
if err := c.processPendingPushNotificationWithReader(ctx, cn, rd); err != nil {
internal.Logger.Printf(ctx, "push: error processing pending notifications before reading reply: %v", err)
}
// Parse number of replies.
line, err := rd.ReadLine()
if err != nil {
if err == Nil {
err = TxFailedErr
}
return err
}
if line[0] != proto.RespArray {
return fmt.Errorf("redis: expected '*', but got line %q", line)
}
return nil
}
//------------------------------------------------------------------------------
// Client is a Redis client representing a pool of zero or more underlying connections.
// It's safe for concurrent use by multiple goroutines.
//
// Client creates and frees connections automatically; it also maintains a free pool
// of idle connections. You can control the pool size with Config.PoolSize option.
type Client struct {
*baseClient
cmdable
// cscLifecycleOwner keeps the canonical Client wrapper (the one whose GC
// cleanup owns the drainer) reachable while a WithTimeout clone can stillView on GitHub (pinned to 36d97525cd)
Solutions
- Check the %q line value in the error — it tells you exactly what the server sent instead of '*'; an error string points at a server-side rejection.
- If a proxy is in the path, bypass it and retry against the real Redis to isolate proxy protocol bugs.
- Ensure the server is not in LOADING state or out-of-memory during the EXEC; both can produce non-array replies.
- Reproduce with redis-cli MULTI/EXEC to confirm the server emits the expected array count.
Defensive patterns
Strategy: retry
Validate before calling
// There is no caller-side input that prevents a protocol desync;
// instead validate the server is RESP-compliant before transacting.
if err := probeClient.Ping(ctx).Err(); err != nil {
return fmt.Errorf("server not healthy for transactions: %w", err)
} Try / catch
err := client.Watch(ctx, func(tx *redis.Tx) error { ... }, key)
if err != nil && strings.Contains(err.Error(), "expected '*'") {
// protocol desync: reconnect and retry once; if it recurs, report server/proxy bug
} Prevention
- Do not place non-Redis-compatible proxies in front of transactional workloads.
- Avoid MULTI/EXEC while the server is in LOADING state or out of memory.
- Capture the %q line from the error in logs — it is the single most useful diagnostic.
When it happens
Trigger: Calling a MULTI/EXEC transaction (TxPipeline, Watch transaction, or any tx-pipelined path) against a server or proxy that does not return the standard RESP array count after QUEUED, or after a connection state where a non-array reply appears mid-transaction.
Common situations: A RESP-incompatible proxy or LSW/APT redis surrogate injected an inline reply; protocol desync caused by an earlier malformed command; an upstream that injects a status/error line instead of the array count; a corrupt TCP stream or TLS middlebox; a server-side OUT-OF-MEMORY or loading reply mid-EXEC.
Related errors
- redis: unexpected EXEC reply %q
- redis: tx pipeline produced no outcome
- redis: invalid line
- redis: please enter the command to be executed
- redis: VectorScoreSliceCmd expects even number of elements,
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/109c4e73cbfc8a2b.json.
Report an issue: GitHub.