redis/go-redis · error

invalid key type at index %d

Error message

invalid key type at index %d

What it means

When parsing an FT.HYBRID flat key/value reply, the client walks the reply array two elements at a time and expects each even-index element to be a string field name. If data[i] is not a string, the reply structure is not what the parser understands, so it fails with the offending index. This indicates the server returned a malformed or unexpected FT.HYBRID reply layout.

Source

Thrown at search_commands.go:3044

func (cmd *FTHybridCmd) RawVal() interface{} {
	cmd.await()
	return cmd.rawVal
}

func (cmd *FTHybridCmd) RawResult() (interface{}, error) {
	cmd.await()
	return cmd.rawVal, cmd.err
}

func parseFTHybrid(data []interface{}, withCursor bool) (FTHybridResult, *FTHybridCursorResult, error) {
	// Convert to map
	resultMap := make(map[string]interface{})
	for i := 0; i < len(data); i += 2 {
		if i+1 < len(data) {
			key, ok := data[i].(string)
			if !ok {
				return FTHybridResult{}, nil, fmt.Errorf("invalid key type at index %d", i)
			}
			resultMap[key] = data[i+1]
		}
	}

	// Handle cursor result
	if withCursor {
		searchCursorID, ok1 := resultMap["SEARCH"].(int64)
		vsimCursorID, ok2 := resultMap["VSIM"].(int64)
		if !ok1 || !ok2 {
			return FTHybridResult{}, nil, fmt.Errorf("invalid cursor result format")
		}
		return FTHybridResult{}, &FTHybridCursorResult{
			SearchCursorID: int(searchCursorID),
			VsimCursorID:   int(vsimCursorID),
		}, nil
	}

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Verify server/RediSearch version matches the go-redis version's expected FT.HYBRID reply format (upgrade go-redis or the server so both agree)
  2. Check protocol negotiation: use Protocol: 3 so the RESP3 map path is taken instead of the flat-array path
  3. Inspect the reply at the reported index with a ProcessHook or MONITOR to see what the server actually returned
  4. Fix mocks/tests to emit string keys at even indices in flat arrays

Example fix

// before (test mock)
[]interface{}{int64(1), "results"}
// after
[]interface{}{"total_results", int64(1), "results", []interface{}{}}
Defensive patterns

Strategy: type-guard

Validate before calling

// pre-check server capabilities before enabling hybrid search
if err := rdb.Do(ctx, "FT.INFO", "idx").Err(); err != nil { /* index/server does not support expected format */ }

Type guard

func isStringKeyedFlatPairs(data []interface{}) bool {
    if len(data)%2 != 0 { return false }
    for i := 0; i < len(data); i += 2 {
        if _, ok := data[i].(string); !ok { return false }
    }
    return true
}

Try / catch

res, err := rdb.FTHybrid(ctx, "idx", query, opts).Result()
if err != nil {
    if strings.Contains(err.Error(), "invalid key type") {
        // log raw reply via hook, flag server/proxy incompatibility
    }
    return err
}

Prevention

When it happens

Trigger: Calling FTHybrid with the flat array parser path when the server reply contains non-string keys at even positions — e.g. a server version emitting different field names, or a RESP2 vs RESP3 reply-shape mismatch being fed to the wrong parser.

Common situations: Running against a Redis stack version whose FT.HYBRID reply layout differs from what this go-redis version expects; using a proxy that converts maps to arrays with binary keys; mock tests returning mis-shaped arrays.

Related errors


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