redis/go-redis · error

redis: can't read raw reply: %.100q

Error message

redis: can't read raw reply: %.100q

What it means

readRawReplyBuf copies the raw bytes of the next reply into a buffer; after failing to match any known RESP type byte on the first line it returns this error with the offending line. Raw-reply reads bypass normal typed parsing, so any unrecognizable type byte (or non-RESP garbage) triggers it.

Source

Thrown at internal/proto/reader.go:954

			}
			return buf, err
		}
		// Read the attribute key-value pairs. Iterate over pairs rather than
		// n*2 elements so a count above MaxInt/2 can't overflow int to a
		// negative loop bound and skip the body.
		for i := 0; i < n; i++ {
			for pair := 0; pair < 2; pair++ {
				buf, err = r.readRawReplyBuf(buf)
				if err != nil {
					return buf, err
				}
			}
		}
		// Read the command reply that follows the attribute
		return r.readRawReplyBuf(buf)
	}

	return buf, fmt.Errorf("redis: can't read raw reply: %.100q", line)
}

var crlf = []byte{'\r', '\n'}

// ReadRawReplyWriteTo streams the next RESP reply directly to w without intermediate allocations.
// Returns the number of bytes written and any error encountered.
func (r *Reader) ReadRawReplyWriteTo(w io.Writer) (int64, error) {
	return r.readRawReplyWriteTo(w)
}

func (r *Reader) readRawReplyWriteTo(w io.Writer) (int64, error) {
	line, err := r.readLine()
	if err != nil {
		return 0, err
	}

	var written int64
	n, err := w.Write(line)

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Reset the connection after errors/timeouts to clear any desync before raw reads
  2. Log the %.100q payload to identify the unexpected first byte
  3. Ensure RESP3 protocol negotiation completed before reading raw replies (attribute handling depends on it)
  4. Verify the server/module actually emits standard RESP types
Defensive patterns

Strategy: type-guard

Validate before calling

// confirm RESP3 handshake before raw reads
if err := client.Do(ctx, "HELLO", "3").Err(); err != nil { /* server lacks RESP3 */ }

Type guard

func isKnownRespType(b byte) bool {
  switch b {
  case '+', '-', ':', '$', '*', '_', '#', ',', '%', '~', '>', '=', '(', '!':
    return true
  }
  return false
}

Try / catch

buf, err := reader.ReadRawReply()
if err != nil && strings.Contains(err.Error(), "can't read raw reply") {
  logger.Warn("unknown reply type on raw read; resetting conn", "err", err)
  _ = conn.Close()
  return err
}

Prevention

When it happens

Trigger: ReadRawReply / readRawReplyBuf encountering a first line whose type byte is unknown — garbage after a desync, an unsupported RESP extension type, or non-Redis data on the stream; also after skipping RespAttr the following line still didn't parse.

Common situations: Raw reply capture features (e.g. debugging hooks or Do/Command raw paths) run against a desynced or proxied connection; custom server modules replying with non-standard types.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/45814aaac5fa5921. Report an issue: GitHub.