{"id":"641ff87388984b7b","repo":"go-redis/redis","slug":"redis-invalid-map-key-v","errorCode":null,"errorMessage":"redis: invalid map key %#v","messagePattern":"redis: invalid map key %#v","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"command.go","lineNumber":6359,"sourceCode":"func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {\n\tcmd.await()\n\treturn cmd.val\n}\n\n// readReply will try to parse the reply from the proto.Reader for both resp2 and resp3\nfunc (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {\n\tdata, err := rd.ReadReply()\n\tif err != nil {\n\t\treturn err\n\t}\n\tresultMap := map[string]interface{}{}\n\n\tswitch midResponse := data.(type) {\n\tcase map[interface{}]interface{}: // resp3 will return map\n\t\tfor k, v := range midResponse {\n\t\t\tstringKey, ok := k.(string)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"redis: invalid map key %#v\", k)\n\t\t\t}\n\t\t\tresultMap[stringKey] = v\n\t\t}\n\tcase []interface{}: // resp2 will return array of arrays\n\t\tn := len(midResponse)\n\t\tfor i := 0; i < n; i++ {\n\t\t\tfinalArr, ok := midResponse[i].([]interface{}) // final array that we need to transform to map\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"redis: unexpected response %#v\", data)\n\t\t\t}\n\t\t\tm := len(finalArr)\n\t\t\tif m%2 != 0 { // since this should be map, keys should be even number\n\t\t\t\treturn fmt.Errorf(\"redis: unexpected response %#v\", data)\n\t\t\t}\n\n\t\t\tfor j := 0; j < m; j += 2 {\n\t\t\t\tstringKey, ok := finalArr[j].(string) // the first one\n\t\t\t\tif !ok {","sourceCodeStart":6341,"sourceCodeEnd":6377,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/command.go#L6341-L6377","documentation":"Thrown by MapMapStringInterfaceCmd.readReply (command.go:6359), used by FT.CONFIG GET. Under RESP3 Redis returns a real map; this parser requires every map key to be a string. If a key asserts to a non-string type, parsing aborts with the offending value.","triggerScenarios":"client.FTConfigGet(ctx, option) over a RESP3 connection (Options.Protocol == 3) when the RediSearch/module reply contains a non-string map key (e.g. an integer or bulk-encoded integer key), or when the reply shape is not what the parser assumes.","commonSituations":"Enabling RESP3 against a RediSearch/module version whose CONFIG GET reply uses non-string keys; a module bug or version mismatch; a proxy that re-types keys.","solutions":["If you do not need RESP3, connect with RESP2 (do not set Options.Protocol=3) so the parser takes the array-of-arrays branch.","Upgrade the RediSearch module and Redis to a version whose FT.CONFIG GET reply uses string keys.","Confirm with redis-cli (`HELLO 3` then `FT.CONFIG GET <option>`) what key types are returned.","Avoid FT.CONFIG GET for unknown/unsupported options that yield unusual replies."],"exampleFix":"// before (RESP3)\nclient := redis.NewClient(&redis.Options{Addr: \":6379\", Protocol: 3})\nv, err := client.FTConfigGet(ctx, \"EXTLOAD\").Result()\n// after (fall back to RESP2 if the module emits non-string keys)\nclient := redis.NewClient(&redis.Options{Addr: \":6379\"}) // Protocol defaults to RESP2","handlingStrategy":"validation","validationCode":"// If RESP3 triggers non-string keys, prefer RESP2 for FT.CONFIG GET.\nif client.Options().Protocol == 3 {\n    // open a separate RESP2 client for module config calls, or drop Protocol:3\n}","typeGuard":null,"tryCatchPattern":"v, err := client.FTConfigGet(ctx, option).Result()\nif err != nil {\n    // retry on a RESP2 connection if RESP3 module reply is malformed\n    v, err = resp2Client.FTConfigGet(ctx, option).Result()\n    return v, err\n}","preventionTips":["Match RESP protocol version to module support.","Keep RediSearch module and go-redis versions compatible."],"tags":["redisearch","ft-config","resp3","resp-parsing"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}