{"record":{"id":"fa889807a4657825","repo":"grpc/grpc-go","slug":"metadata-fromoutgoingcontext-got-an-odd-number-of","errorCode":null,"errorMessage":"metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d","messagePattern":"metadata: FromOutgoingContext got an odd number of input pairs for metadata: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"metadata/metadata.go","lineNumber":352,"sourceCode":"\t\treturn nil, false\n\t}\n\n\tmdSize := len(raw.md)\n\tfor i := range raw.added {\n\t\tmdSize += len(raw.added[i]) / 2\n\t}\n\n\tout := make(MD, mdSize)\n\tfor k, v := range raw.md {\n\t\t// We need to manually convert all keys to lower case, because MD is a\n\t\t// map, and there's no guarantee that the MD attached to the context is\n\t\t// created using our helper functions.\n\t\tkey := strings.ToLower(k)\n\t\tout[key] = copyOf(v)\n\t}\n\tfor _, added := range raw.added {\n\t\tif len(added)%2 == 1 {\n\t\t\tpanic(fmt.Sprintf(\"metadata: FromOutgoingContext got an odd number of input pairs for metadata: %d\", len(added)))\n\t\t}\n\n\t\tfor i := 0; i < len(added); i += 2 {\n\t\t\tkey := strings.ToLower(added[i])\n\t\t\tout[key] = append(out[key], added[i+1])\n\t\t}\n\t}\n\treturn out, ok\n}\n\ntype rawMD struct {\n\tmd    MD\n\tadded [][]string\n}\n","sourceCodeStart":334,"sourceCodeEnd":367,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/metadata/metadata.go#L334-L367","documentation":"metadata.FromOutgoingContext (metadata.go:331) reads outgoing metadata back out of a context. It panics when one of the internally-stored appended slices has an odd length (the len(added)%2==1 check at line 351). Unlike Pairs/AppendToOutgoingContext, this is a READ-time panic: it indicates the context's outgoing metadata was corrupted after the fact, not that the FromOutgoingContext caller passed odd arguments (it takes none).","triggerScenarios":"FromOutgoingContext(ctx) panics because rawMD.added (populated via AppendToOutgoingContext) contains an odd-length slice. This should be impossible through the public API; it happens when something manipulates the context value directly (reflection, an internal/grpcx helper, an older-incompatible grpc version, or a buggy custom context wrapper) and writes a malformed rawMD.","commonSituations":"A custom middleware or test helper that injects metadata into the context via reflection or a copied internal struct; vendoring mismatched grpc versions where the rawMD layout changed; a fork of grpc with a bug in AppendToOutgoingContext.","solutions":["Audit any code that writes to the mdOutgoingKey context value directly and route all metadata through the public AppendToOutgoingContext / NewOutgoingContext APIs.","Ensure a single consistent grpc-go version across modules (go mod tidy / check for duplicates with 'go list -m all | grep grpc').","If using a grpc fork, update it to match upstream AppendToOutgoingContext behavior."],"exampleFix":"// before: a test helper pokes the context value directly with an odd slice\nctx = context.WithValue(ctx, mdOutgoingKey{}, rawMD{added: [][]string{{\"only-key\"}}})\nout, _ := metadata.FromOutgoingContext(ctx) // panics at read time\n\n// after: always use the public API so parity is guaranteed\nctx = metadata.AppendToOutgoingContext(ctx, \"key\", \"value\")\nout, _ := metadata.FromOutgoingContext(ctx)","handlingStrategy":"validation","validationCode":"// FromOutgoingContext panics on internally-corrupted data.\n// Prevent it by NEVER writing the mdOutgoingKey context value directly;\n// only use metadata.NewOutgoingContext / AppendToOutgoingContext.\n// If you must read untrusted contexts, recover defensively:\nfunc safeFromOutgoing(ctx context.Context) (md metadata.MD, ok bool) {\n    defer func() { _ = recover() }() // last resort only\n    return metadata.FromOutgoingContext(ctx)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never inject or mutate the mdOutgoingKey context value via reflection or copied internal structs.","Keep a single grpc-go version across all modules to avoid rawMD layout mismatches.","Audit custom context wrappers and test helpers that touch outgoing metadata."],"tags":["metadata","panic","grpc-go","context","corruption"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}