go-redis/redis · error
redis: ZeroCopyStringCmd cannot be cloned (cmd writes into c
Error message
redis: ZeroCopyStringCmd cannot be cloned (cmd writes into caller-owned memory)
What it means
Returned by ZeroCopyStringCmd.readReply (command.go:1098) when the cmd instance was produced by Clone(). ZeroCopyStringCmd writes its reply directly into a caller-owned buffer (the buf passed to GetToBuffer); a clone can neither share that buffer safely (concurrent sibling writes would race) nor allocate its own (the result would be invisible to the original caller). Clone therefore returns a marked clone that drains the network reply (to keep the connection aligned) and surfaces this explicit error instead of silently-wrong bytes. In practice the path is unreachable through normal flows: GetToBuffer issues a single-key GET that is never fanned out by cluster routing, and NoRetry()==true prevents the retry path from cloning it.
Source
Thrown at command.go:1098
func (cmd *ZeroCopyStringCmd) String() string {
cmd.await()
return cmdString(cmd, cmd.n)
}
func (cmd *ZeroCopyStringCmd) readReply(rd *proto.Reader) error {
// Reset the byte count before reading so a previous successful run
// can't leak its data through Bytes() if this call errors out before
// updating cmd.n.
cmd.n = 0
if cmd.cloned {
// A cloned ZeroCopyStringCmd has no usable destination buffer
// (see Clone for the rationale). Drain the network reply so the
// connection stays aligned for the next command, then surface a
// clear error rather than silently producing a wrong result.
if err := rd.DiscardNext(); err != nil {
return err
}
return fmt.Errorf("redis: ZeroCopyStringCmd cannot be cloned (cmd writes into caller-owned memory)")
}
n, err := rd.ReadStringInto(cmd.buf)
if err != nil {
return err
}
cmd.n = n
return nil
}
// NoRetry returns true because the response is written directly into the
// caller's buffer. A retry could leave partial data from a failed attempt in
// the buffer, so the caller must handle retries explicitly if needed.
func (cmd *ZeroCopyStringCmd) NoRetry() bool {
return true
}
// Clone returns a clone that is intentionally non-functional. Cloning a
// ZeroCopyStringCmd has no well-defined semantics: the cmd writes intoView on GitHub (pinned to 36d97525cd)
Solutions
- Do not Clone a ZeroCopyStringCmd; if you need a second read, issue a fresh GetToBuffer.
- For multi-key/cluster-fanout workloads use the regular Get (StringCmd) which is fully cloneable and retryable.
- If using pipelines, remember ZeroCopyStringCmd has NoRetry()==true — handle retries explicitly rather than via the library's automatic clone-and-retry.
Example fix
// before (conceptual — not reachable via normal APIs) clone := cmd.(*redis.ZeroCopyStringCmd).Clone() _ = client.Process(ctx, clone) // returns the cannot-be-cloned error // after — issue a fresh zero-copy read fresh := client.GetToBuffer(ctx, key, newBuf)
Defensive patterns
Strategy: validation
Validate before calling
// Never clone a ZeroCopyStringCmd. If you need a second read, issue a fresh GetToBuffer.
// Use the regular Get (StringCmd) for any path that may clone (cluster fan-out, retry).
if _, ok := cmd.(*redis.ZeroCopyStringCmd); ok {
// do not call Clone(); do not feed into cluster fan-out/retry paths
} Type guard
func isZeroCopy(cmd redis.Cmder) bool {
_, ok := cmd.(*redis.ZeroCopyStringCmd)
return ok
} Prevention
- Use regular Get (not GetToBuffer) for multi-key or cluster-fanout workloads.
- Remember ZeroCopyStringCmd has NoRetry()==true — handle retries yourself.
- Never call Clone() on a ZeroCopyStringCmd; issue a fresh read instead.
When it happens
Trigger: Theoretically: cluster fan-out routing (osscluster_router.go) attempting to clone a ZeroCopyStringCmd, or a retry path cloning it. Practically unreachable because GetToBuffer is single-key and NoRetry is true. Surfaceable only if custom code forces a Clone on a ZeroCopyStringCmd and then dispatches it.
Common situations: Essentially never seen in normal use. Possible in custom cluster-routing experiments, or if a user manually clones pipeline commands and re-executes them.
Related errors
- redis: unexpected type=%T for String
- redis: unexpected type=%T for Int
- redis: unexpected type=%T for Int64
- redis: unexpected type=%T for Uint64
- redis: unexpected type=%T for Float32
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/d4b757d4aa447a0b.json.
Report an issue: GitHub.