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
- Verify server/RediSearch version matches the go-redis version's expected FT.HYBRID reply format (upgrade go-redis or the server so both agree)
- Check protocol negotiation: use Protocol: 3 so the RESP3 map path is taken instead of the flat-array path
- Inspect the reply at the reported index with a ProcessHook or MONITOR to see what the server actually returned
- 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
- Keep client and server versions aligned
- Use Protocol: 3 so the map parsing path is used
- Validate test fixtures against the documented FT.HYBRID reply format
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
- invalid total_results format
- invalid results format
- invalid result item format
- invalid item key format
- invalid cursor result format
AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01).
Data as JSON: /api/errors/d82917e8073c49a4.
Report an issue: GitHub.