go-redis/redis · error

odd-length key/value array of length

Error message

odd-length key/value array of length %d

What it means

Returned by parseCollectEntry in the RESP2 branch when a flat key/value array has an odd number of elements. RESP2 represents each COLLECT entry as [field, value, field, value, ...], so an odd length means the frame is truncated or corrupt. The length is included to show how off it is.

Solutions

  1. Check the server Redis version and patch level against the COLLECT feature requirements (8.8+).
  2. Capture the raw reply (cmd.RawResult()) and inspect the offending entry to confirm truncation.
  3. If a proxy is in front of Redis, bypass it to see if the corruption originates upstream.
  4. Report to the server/proxy maintainer with the raw frame; this shape should never be emitted by a conforming COLLECT implementation.
Defensive patterns

Strategy: try-catch

Type guard

func isEvenKVArray(e interface{}) bool {
	arr, ok := e.([]interface{})
	return ok && len(arr)%2 == 0
}

Try / catch

col, err := row.Collect(alias)
if err != nil && strings.Contains(err.Error(), "odd-length key/value array") {
    // server/proxy returned a truncated RESP2 entry; report raw frame
    raw, _ := cmd.RawResult()
    log.Printf("malformed COLLECT entry, raw=%v", raw)
}
return err

Prevention

When it happens

Trigger: AggregateRow.Collect decoding a RESP2 reply where one entry array has odd length (e.g. 3 or 5 elements). Surfaces during response decoding only; cannot be produced by local API misuse.

Common situations: A server/proxy bug truncating the entry array. RESP2/RESP3 confusion where a partial map leaks through as a half-array. Field count mismatch between what COLLECT projected and what the server emitted. Network-level frame corruption (rare).

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /api/errors/2238b893705e0bad. Report an issue: GitHub.

Appendix: source

Thrown at search_collect.go:221

}

// parseCollectEntry decodes a single collected entry from either the RESP3
// map form or the RESP2 flat key/value array form into a CollectEntry. Keys
// are passed through as-is: the server already returns them without the "@"
// prefix.
func parseCollectEntry(e interface{}) (CollectEntry, error) {
	switch m := e.(type) {
	case map[interface{}]interface{}: // RESP3
		out := make(CollectEntry, len(m))
		for k, val := range m {
			out[fmt.Sprint(k)] = val
		}
		return out, nil
	case map[string]interface{}: // already string-keyed
		return m, nil
	case []interface{}: // RESP2 flat [field, value, field, value, ...]
		if len(m)%2 != 0 {
			return nil, fmt.Errorf("odd-length key/value array of length %d", len(m))
		}
		out := make(CollectEntry, len(m)/2)
		for i := 0; i < len(m); i += 2 {
			key, ok := m[i].(string)
			if !ok {
				key = fmt.Sprint(m[i])
			}
			out[key] = m[i+1]
		}
		return out, nil
	default:
		return nil, fmt.Errorf("unexpected type %T, want map or key/value array", e)
	}
}

View on GitHub (pinned to 36d97525cd)