grpc/grpc-go · critical
metadata: Pairs got the odd number of input pairs for…
Error message
metadata: Pairs got the odd number of input pairs for metadata: %d
What it means
metadata.Pairs (metadata/metadata.go:82) builds an MD from alternating key/value string arguments and panics at line 83-85 when len(kv) is odd, because a dangling key with no value is meaningless. Keys are lowercased and stored; values are appended. The doc (line 70) states Pairs panics on odd input.
Solutions
- Always pass key/value pairs: an even number of string arguments.
- When building the slice dynamically, assert len(kv)%2 == 0 before calling Pairs.
- Use metadata.New(map[string]string{...}) or md.Append(k, v) for less error-prone construction.
Example fix
// before
md := metadata.Pairs("trace-id", traceID, "user-id") // odd -> panic
// after
md := metadata.Pairs("trace-id", traceID, "user-id", userID)
// or
md := metadata.New(map[string]string{"trace-id": traceID, "user-id": userID}) Defensive patterns
Strategy: validation
Validate before calling
// Validate parity before calling Pairs.
func safePairs(kv ...string) metadata.MD {
if len(kv)%2 == 1 {
return metadata.MD{} // or return an error from your wrapper
}
return metadata.Pairs(kv...)
} Prevention
- Pass an even number of arguments (key, value, key, value, ...).
- When building kv dynamically, assert len(kv)%2 == 0 first.
- Prefer metadata.New(map[string]string{...}) for less error-prone construction.
When it happens
Trigger: Calling metadata.Pairs("key") (one arg), metadata.Pairs("a","1","b") (three args), or building the kv slice dynamically and accidentally dropping a value.
Common situations: A typo omitting a value; programmatically appending metadata keys and missing the paired value; a refactor that splits a pair across a conditional.
Related errors
- metadata: AppendToOutgoingContext got an odd number of…
- metadata: FromOutgoingContext got an odd number of input…
- Cannot free freed buffer
- cannot have a leading slash
- cannot have exclude and a '*' wildcard
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/e0ed8db280ac0ee1.
Report an issue: GitHub.
Appendix: source
Thrown at metadata/metadata.go:84
return md
}
// Pairs returns an MD formed by the mapping of key, value ...
// Pairs panics if len(kv) is odd.
//
// Only the following ASCII characters are allowed in keys:
// - digits: 0-9
// - uppercase letters: A-Z (normalized to lower)
// - lowercase letters: a-z
// - special characters: -_.
//
// Uppercase letters are automatically converted to lowercase.
//
// Keys beginning with "grpc-" are reserved for grpc-internal use only and may
// result in errors if set in metadata.
func Pairs(kv ...string) MD {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
}
md := make(MD, len(kv)/2)
for i := 0; i < len(kv); i += 2 {
key := strings.ToLower(kv[i])
md[key] = append(md[key], kv[i+1])
}
return md
}
// loggableMetadataKeys is the set of metadata keys whose values are known not
// to carry credentials or other sensitive data, and are therefore safe to print
// verbatim in String. Values for any key not in this set are redacted. Keys are
// in metadata's canonical lowercase form.
//
// The list is intentionally restricted to standardized gRPC and HTTP/2 protocol
// headers. Everything else, including all application-defined keys, is
// censored.
// Note that this list might change as we add/remove support forView on GitHub (pinned to 0c51461d27)