{"record":{"id":"45814aaac5fa5921","repo":"redis/go-redis","slug":"redis-can-t-read-raw-reply-100q","errorCode":null,"errorMessage":"redis: can't read raw reply: %.100q","messagePattern":"redis: can't read raw reply: %\\.100q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/proto/reader.go","lineNumber":954,"sourceCode":"\t\t\t}\n\t\t\treturn buf, err\n\t\t}\n\t\t// Read the attribute key-value pairs. Iterate over pairs rather than\n\t\t// n*2 elements so a count above MaxInt/2 can't overflow int to a\n\t\t// negative loop bound and skip the body.\n\t\tfor i := 0; i < n; i++ {\n\t\t\tfor pair := 0; pair < 2; pair++ {\n\t\t\t\tbuf, err = r.readRawReplyBuf(buf)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn buf, err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Read the command reply that follows the attribute\n\t\treturn r.readRawReplyBuf(buf)\n\t}\n\n\treturn buf, fmt.Errorf(\"redis: can't read raw reply: %.100q\", line)\n}\n\nvar crlf = []byte{'\\r', '\\n'}\n\n// ReadRawReplyWriteTo streams the next RESP reply directly to w without intermediate allocations.\n// Returns the number of bytes written and any error encountered.\nfunc (r *Reader) ReadRawReplyWriteTo(w io.Writer) (int64, error) {\n\treturn r.readRawReplyWriteTo(w)\n}\n\nfunc (r *Reader) readRawReplyWriteTo(w io.Writer) (int64, error) {\n\tline, err := r.readLine()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar written int64\n\tn, err := w.Write(line)","sourceCodeStart":936,"sourceCodeEnd":972,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/internal/proto/reader.go#L936-L972","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reset the connection after errors/timeouts to clear any desync before raw reads","Log the %.100q payload to identify the unexpected first byte","Ensure RESP3 protocol negotiation completed before reading raw replies (attribute handling depends on it)","Verify the server/module actually emits standard RESP types"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// confirm RESP3 handshake before raw reads\nif err := client.Do(ctx, \"HELLO\", \"3\").Err(); err != nil { /* server lacks RESP3 */ }","typeGuard":"func isKnownRespType(b byte) bool {\n  switch b {\n  case '+', '-', ':', '$', '*', '_', '#', ',', '%', '~', '>', '=', '(', '!':\n    return true\n  }\n  return false\n}","tryCatchPattern":"buf, err := reader.ReadRawReply()\nif err != nil && strings.Contains(err.Error(), \"can't read raw reply\") {\n  logger.Warn(\"unknown reply type on raw read; resetting conn\", \"err\", err)\n  _ = conn.Close()\n  return err\n}","preventionTips":["Complete RESP3 negotiation before raw reply capture","Reset connections after any prior parse error","Verify custom modules emit standard RESP types","Inspect the logged %.100q line to identify unknown type bytes"],"tags":["go-redis","resp-protocol","raw-reply","parsing"],"backgroundTag":"resp-reply-parse-failure","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}