go-redis/redis · error

redis: invalid map key %#v

Error message

redis: invalid map key %#v

What it means

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.

Source

Thrown at command.go:6359

func (cmd *MapMapStringInterfaceCmd) Val() map[string]interface{} {
	cmd.await()
	return cmd.val
}

// readReply will try to parse the reply from the proto.Reader for both resp2 and resp3
func (cmd *MapMapStringInterfaceCmd) readReply(rd *proto.Reader) (err error) {
	data, err := rd.ReadReply()
	if err != nil {
		return err
	}
	resultMap := map[string]interface{}{}

	switch midResponse := data.(type) {
	case map[interface{}]interface{}: // resp3 will return map
		for k, v := range midResponse {
			stringKey, ok := k.(string)
			if !ok {
				return fmt.Errorf("redis: invalid map key %#v", k)
			}
			resultMap[stringKey] = v
		}
	case []interface{}: // resp2 will return array of arrays
		n := len(midResponse)
		for i := 0; i < n; i++ {
			finalArr, ok := midResponse[i].([]interface{}) // final array that we need to transform to map
			if !ok {
				return fmt.Errorf("redis: unexpected response %#v", data)
			}
			m := len(finalArr)
			if m%2 != 0 { // since this should be map, keys should be even number
				return fmt.Errorf("redis: unexpected response %#v", data)
			}

			for j := 0; j < m; j += 2 {
				stringKey, ok := finalArr[j].(string) // the first one
				if !ok {

View on GitHub (pinned to 36d97525cd)

Solutions

  1. If you do not need RESP3, connect with RESP2 (do not set Options.Protocol=3) so the parser takes the array-of-arrays branch.
  2. Upgrade the RediSearch module and Redis to a version whose FT.CONFIG GET reply uses string keys.
  3. Confirm with redis-cli (`HELLO 3` then `FT.CONFIG GET <option>`) what key types are returned.
  4. Avoid FT.CONFIG GET for unknown/unsupported options that yield unusual replies.

Example fix

// before (RESP3)
client := redis.NewClient(&redis.Options{Addr: ":6379", Protocol: 3})
v, err := client.FTConfigGet(ctx, "EXTLOAD").Result()
// after (fall back to RESP2 if the module emits non-string keys)
client := redis.NewClient(&redis.Options{Addr: ":6379"}) // Protocol defaults to RESP2
Defensive patterns

Strategy: validation

Validate before calling

// If RESP3 triggers non-string keys, prefer RESP2 for FT.CONFIG GET.
if client.Options().Protocol == 3 {
    // open a separate RESP2 client for module config calls, or drop Protocol:3
}

Try / catch

v, err := client.FTConfigGet(ctx, option).Result()
if err != nil {
    // retry on a RESP2 connection if RESP3 module reply is malformed
    v, err = resp2Client.FTConfigGet(ctx, option).Result()
    return v, err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/641ff87388984b7b.json. Report an issue: GitHub.