{"id":"2238b893705e0bad","repo":"go-redis/redis","slug":"odd-length-key-value-array-of-length-d","errorCode":null,"errorMessage":"odd-length key/value array of length %d","messagePattern":"odd-length key/value array of length (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"search_collect.go","lineNumber":221,"sourceCode":"}\n\n// parseCollectEntry decodes a single collected entry from either the RESP3\n// map form or the RESP2 flat key/value array form into a CollectEntry. Keys\n// are passed through as-is: the server already returns them without the \"@\"\n// prefix.\nfunc parseCollectEntry(e interface{}) (CollectEntry, error) {\n\tswitch m := e.(type) {\n\tcase map[interface{}]interface{}: // RESP3\n\t\tout := make(CollectEntry, len(m))\n\t\tfor k, val := range m {\n\t\t\tout[fmt.Sprint(k)] = val\n\t\t}\n\t\treturn out, nil\n\tcase map[string]interface{}: // already string-keyed\n\t\treturn m, nil\n\tcase []interface{}: // RESP2 flat [field, value, field, value, ...]\n\t\tif len(m)%2 != 0 {\n\t\t\treturn nil, fmt.Errorf(\"odd-length key/value array of length %d\", len(m))\n\t\t}\n\t\tout := make(CollectEntry, len(m)/2)\n\t\tfor i := 0; i < len(m); i += 2 {\n\t\t\tkey, ok := m[i].(string)\n\t\t\tif !ok {\n\t\t\t\tkey = fmt.Sprint(m[i])\n\t\t\t}\n\t\t\tout[key] = m[i+1]\n\t\t}\n\t\treturn out, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unexpected type %T, want map or key/value array\", e)\n\t}\n}\n","sourceCodeStart":203,"sourceCodeEnd":236,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/search_collect.go#L203-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","solutions":["Check the server Redis version and patch level against the COLLECT feature requirements (8.8+).","Capture the raw reply (cmd.RawResult()) and inspect the offending entry to confirm truncation.","If a proxy is in front of Redis, bypass it to see if the corruption originates upstream.","Report to the server/proxy maintainer with the raw frame; this shape should never be emitted by a conforming COLLECT implementation."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isEvenKVArray(e interface{}) bool {\n\tarr, ok := e.([]interface{})\n\treturn ok && len(arr)%2 == 0\n}","tryCatchPattern":"col, err := row.Collect(alias)\nif err != nil && strings.Contains(err.Error(), \"odd-length key/value array\") {\n    // server/proxy returned a truncated RESP2 entry; report raw frame\n    raw, _ := cmd.RawResult()\n    log.Printf(\"malformed COLLECT entry, raw=%v\", raw)\n}\nreturn err","preventionTips":["Run COLLECT against a conforming Redis 8.8+ server; treat odd-length arrays as a server/proxy defect.","Bypass any RESP-mangling proxy when diagnosing to localize the source.","Log cmd.RawVal() alongside the decode error for forensic review."],"tags":["ft-aggregate","collect","response-decoding","resp2","corruption"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}