{"record":{"id":"e1d74469e1edc335","repo":"grpc/grpc-go","slug":"metadata-appendtooutgoingcontext-got-an-odd-numbe","errorCode":null,"errorMessage":"metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d","messagePattern":"metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"metadata/metadata.go","lineNumber":252,"sourceCode":"// not be modified after calling this function.\nfunc NewIncomingContext(ctx context.Context, md MD) context.Context {\n\treturn context.WithValue(ctx, mdIncomingKey{}, md)\n}\n\n// NewOutgoingContext creates a new context with outgoing md attached. If used\n// in conjunction with AppendToOutgoingContext, NewOutgoingContext will\n// overwrite any previously-appended metadata. md must not be modified after\n// calling this function.\nfunc NewOutgoingContext(ctx context.Context, md MD) context.Context {\n\treturn context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md})\n}\n\n// AppendToOutgoingContext returns a new context with the provided kv merged\n// with any existing metadata in the context. Please refer to the documentation\n// of Pairs for a description of kv.\nfunc AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context {\n\tif len(kv)%2 == 1 {\n\t\tpanic(fmt.Sprintf(\"metadata: AppendToOutgoingContext got an odd number of input pairs for metadata: %d\", len(kv)))\n\t}\n\tmd, _ := ctx.Value(mdOutgoingKey{}).(rawMD)\n\tadded := make([][]string, len(md.added)+1)\n\tcopy(added, md.added)\n\tkvCopy := make([]string, 0, len(kv))\n\tfor i := 0; i < len(kv); i += 2 {\n\t\tkvCopy = append(kvCopy, strings.ToLower(kv[i]), kv[i+1])\n\t}\n\tadded[len(added)-1] = kvCopy\n\treturn context.WithValue(ctx, mdOutgoingKey{}, rawMD{md: md.md, added: added})\n}\n\n// FromIncomingContext returns the incoming metadata in ctx if it exists.\n//\n// All keys in the returned MD are lowercase.\nfunc FromIncomingContext(ctx context.Context) (MD, bool) {\n\tmd, ok := ctx.Value(mdIncomingKey{}).(MD)\n\tif !ok {","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/metadata/metadata.go#L234-L270","documentation":"metadata.AppendToOutgoingContext (metadata.go:250) appends key/value pairs to the outgoing metadata already attached to a context. Like Pairs, it panics on an odd argument count because each key must be followed by exactly one value. This is called per-RPC (inside request building), so the panic occurs in the hot path and will crash the serving goroutine.","triggerScenarios":"Calling metadata.AppendToOutgoingContext(ctx, kv...) with an odd number of arguments, e.g. AppendToOutgoingContext(ctx, \"auth\", token, \"extra\"). The len(kv)%2==1 guard fires immediately.","commonSituations":"Per-RPC interceptor or handler appending a header but forgetting the value; a conditional append that added the key branch but not the value; refactoring that dropped an argument.","solutions":["Locate the AppendToOutgoingContext call in the panic stack trace and ensure arguments are even and paired.","For conditional headers, always append both the key and the value together inside the same branch.","Add a unit test that asserts len(args)%2==0 for any dynamic pair slice you build."],"exampleFix":"// before\nctx = metadata.AppendToOutgoingContext(ctx, \"authorization\", token, \"caller\") // odd\n\n// after\nctx = metadata.AppendToOutgoingContext(ctx, \"authorization\", token, \"caller\", callerID)","handlingStrategy":"validation","validationCode":"// Guard dynamic per-RPC metadata before appending\nif len(pairs)%2 != 0 {\n    return ctx, fmt.Errorf(\"AppendToOutgoingContext needs even pairs, got %d\", len(pairs))\n}\nreturn metadata.AppendToOutgoingContext(ctx, pairs...), nil","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Append conditional headers as complete (key,value) units inside one branch.","Avoid concatenating slices to build the kv list; append in twos.","Unit-test interceptors that call AppendToOutgoingContext with varying inputs."],"tags":["metadata","panic","grpc-go","context","validation"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}