redis/go-redis · error

redis: can't parse reply=%.100q reading string

Error message

redis: can't parse reply=%.100q reading string

What it means

ReadString could not interpret the reply line as any string-compatible RESP type. The parser exhausted its switch over known type bytes (simple string, error, integer, bulk, verbatim, nil, bool variants) and falls through to this error, including the offending line truncated to 100 chars. It indicates the reply type is unexpected for a string read or the stream is desynchronized.

Source

Thrown at internal/proto/reader.go:690

	switch line[0] {
	case RespStatus, RespInt, RespFloat:
		return string(line[1:]), nil
	case RespString:
		return r.readStringReply(line)
	case RespBool:
		b, err := r.readBool(line)
		return strconv.FormatBool(b), err
	case RespVerbatim:
		return r.readVerb(line)
	case RespBigInt:
		b, err := r.readBigInt(line)
		if err != nil {
			return "", err
		}
		return b.String(), nil
	}
	return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line)
}

func (r *Reader) ReadBool() (bool, error) {
	s, err := r.ReadString()
	if err != nil {
		return false, err
	}
	return s == "OK" || s == "1" || s == "true", nil
}

func (r *Reader) ReadSlice() ([]interface{}, error) {
	line, err := r.ReadLine()
	if err != nil {
		return nil, err
	}
	return r.readSlice(line)
}

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Set Protocol: 3 in Options if the server emits RESP3-only types (maps, verbatim strings) your code path parses as strings
  2. Check that the command used actually returns a string at this position (inspect with redis-cli --raw)
  3. Ensure no earlier reply was dropped, which desyncs all subsequent line parsing
  4. Upgrade go-redis if a newer Redis version changed a command's reply shape

Example fix

// before
opt := &redis.Options{Addr: addr}
// after: opt in RESP3 so map/verbatim replies parse correctly
opt := &redis.Options{Addr: addr, Protocol: 3}
Defensive patterns

Strategy: type-guard

Validate before calling

// confirm the command returns a string at this position
// e.g. redis-cli --raw XINFO GROUPS mystream

Type guard

func isStringableReply(t byte) bool {
  return t == '+' || t == '$' || t == '=' || t == ':'
}

Try / catch

s, err := reader.ReadString()
if err != nil {
  if strings.Contains(err.Error(), "can't parse reply") {
    logger.Warn("unexpected reply type; resetting conn", "err", err)
    _ = conn.Close()
  }
  return err
}

Prevention

When it happens

Trigger: readReply or stream parsers (readXMessage, stringInterfaceMapParser, readStreamGroups, readXInfoStream*P*) encountering a reply element that is not a string where one is expected — e.g. RESP3 map/verbatim reply under RESP2 mode, or a NIL element where a string is required.

Common situations: XREADGROUP/XINFO replies parsed against an unexpected server version output; RESP2 vs RESP3 mismatch (Protocol option) so maps arrive as arrays with different shapes; corrupt stream after an interrupted connection reuse.

Related errors


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