{"record":{"id":"264abc34107ce132","repo":"grafana/k6","slug":"q-value-must-be-binary","errorCode":null,"errorMessage":"%q value must be binary","messagePattern":"%q value must be binary","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/params.go","lineNumber":94,"sourceCode":"\tif common.IsNullish(input) {\n\t\treturn md, nil\n\t}\n\n\tv := input.Export()\n\n\trawHeaders, ok := v.(map[string]any)\n\tif !ok {\n\t\treturn md, errors.New(\"must be an object with key-value pairs\")\n\t}\n\n\tfor hk, kv := range rawHeaders {\n\t\tvar val string\n\t\t// The gRPC spec defines that Binary-valued keys end in -bin\n\t\t// https://grpc.io/docs/what-is-grpc/core-concepts/#metadata\n\t\tif strings.HasSuffix(hk, \"-bin\") {\n\t\t\tvar binVal []byte\n\t\t\tif binVal, ok = kv.([]byte); !ok {\n\t\t\t\treturn md, fmt.Errorf(\"%q value must be binary\", hk)\n\t\t\t}\n\n\t\t\t// https://github.com/grpc/grpc-go/blob/v1.57.0/Documentation/grpc-metadata.md#storing-binary-data-in-metadata\n\t\t\tval = string(binVal)\n\t\t} else if val, ok = kv.(string); !ok {\n\t\t\treturn md, fmt.Errorf(\"%q value must be a string\", hk)\n\t\t}\n\n\t\tmd.Append(hk, val)\n\t}\n\n\treturn md, nil\n}\n\n// SetSystemTags sets the system tags for the call.\nfunc (p *callParams) SetSystemTags(state *lib.State, addr string, methodName string) {\n\tif state.Options.SystemTags.Has(metrics.TagURL) {\n\t\tp.TagsAndMeta.SetSystemTagOrMeta(metrics.TagURL, fmt.Sprintf(\"%s%s\", addr, methodName))","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/params.go#L76-L112","documentation":"Per the gRPC spec, metadata keys ending in -bin carry binary values. newMetadata type-asserts such values to []byte after exporting them from JS, so a Uint8Array (or ArrayBuffer) is required. Strings, numbers or plain JS arrays for a -bin key throw '%q value must be binary'.","triggerScenarios":"{ 'x-load-tester-bin': 'MG==' } (base64 string); { 'session-bin': [2, 200] } (plain Array, not a typed array); numeric values on a -bin key.","commonSituations":"Encoders returning base64 strings instead of bytes; assuming JSON arrays serialize as binary; porting metadata from grpcurl, which accepts base64 for -bin keys.","solutions":["Wrap bytes in a typed array: new Uint8Array([2, 200])","Or decode base64 first: encoding.b64decode(s) from 'k6/encoding' returns an ArrayBuffer, which is accepted","Keep -bin suffixes only on keys whose values are genuinely binary"],"exampleFix":"// before\n{ metadata: { 'x-load-tester-bin': 'buffer' } }\n\n// after\n{ metadata: { 'x-load-tester-bin': new Uint8Array([2, 200]) } }","handlingStrategy":"type-guard","validationCode":"function isBinaryValue(v) {\n  return v instanceof Uint8Array || v instanceof ArrayBuffer;\n}\nfor (const [k, v] of Object.entries(params.metadata || {})) {\n  if (k.endsWith('-bin') && !isBinaryValue(v)) {\n    throw new Error(`metadata['${k}'] must be a Uint8Array/ArrayBuffer`);\n  }\n}\nclient.invoke(method, req, params);","typeGuard":"function isBinaryMetadataValue(v) {\n  return v instanceof Uint8Array || v instanceof ArrayBuffer;\n}","tryCatchPattern":null,"preventionTips":["Use new Uint8Array([...]) or encoding.b64decode(...) for -bin keys","Decode base64 before attaching it to a -bin key","Do not use plain JS arrays for binary values"],"tags":["grpc","metadata","binary","type-error"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}