{"record":{"id":"657c854159db2252","repo":"grafana/k6","slug":"unknown-param-q","errorCode":null,"errorMessage":"unknown param: %q","messagePattern":"unknown param: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/grpc/params.go","lineNumber":65,"sourceCode":"\t\t\t\treturn result, fmt.Errorf(\"invalid metadata param: %w\", err)\n\t\t\t}\n\n\t\t\tresult.Metadata = md\n\t\tcase \"tags\":\n\t\t\tif err := common.ApplyCustomUserTags(rt, &result.TagsAndMeta, params.Get(k)); err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"metric tags: %w\", err)\n\t\t\t}\n\t\tcase \"timeout\":\n\t\t\tvar err error\n\t\t\tv := params.Get(k).Export()\n\t\t\tresult.Timeout, err = types.GetDurationValue(v)\n\t\t\tif err != nil {\n\t\t\t\treturn result, fmt.Errorf(\"invalid timeout value: %w\", err)\n\t\t\t}\n\t\tcase \"discardResponseMessage\":\n\t\t\tresult.DiscardResponseMessage = params.Get(k).ToBoolean()\n\t\tdefault:\n\t\t\treturn result, fmt.Errorf(\"unknown param: %q\", k)\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n// newMetadata constructs a metadata.MD from the input value.\nfunc newMetadata(input sobek.Value) (metadata.MD, error) {\n\tmd := metadata.New(nil)\n\n\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 {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/grpc/params.go#L47-L83","documentation":"newCallParams accepts exactly four keys: metadata, tags, timeout, discardResponseMessage. The switch's default branch rejects every other property in the params object passed to client.invoke() or new grpc.Stream(), so a single unknown or misspelled key fails the whole call with \"unknown param\".","triggerScenarios":"{ headers: {...} } (HTTP-style naming); { discardResponseMessages: true } (plural typo); { name: 'checkout' } (not a grpc call param); { auth: 'token' }.","commonSituations":"k6/http users reusing options objects for grpc calls; shared param helpers; key lists drifting between k6 versions or between modules.","solutions":["Rename headers to metadata","Remove unsupported keys; set the metric name via tags: { name: 'checkout' }","Whitelist-validate the params object before passing it (see defense)"],"exampleFix":"// before\nclient.invoke(method, req, { headers: { 'x-tag': 'v' }, name: 'checkout' });\n\n// after\nclient.invoke(method, req, { metadata: { 'x-tag': 'v' }, tags: { name: 'checkout' } });","handlingStrategy":"validation","validationCode":"const CALL_PARAM_KEYS = new Set(['metadata', 'tags', 'timeout', 'discardResponseMessage']);\nfunction hasOnlyKnownCallParams(p) {\n  return p == null || Object.keys(p).every((k) => CALL_PARAM_KEYS.has(k));\n}\nif (!hasOnlyKnownCallParams(params)) {\n  const bad = Object.keys(params).filter((k) => !CALL_PARAM_KEYS.has(k));\n  throw new Error(`unknown grpc call params: ${bad.join(', ')}`);\n}","typeGuard":"function isCallParamsObject(p) {\n  if (p == null) return true;\n  const allowed = new Set(['metadata', 'tags', 'timeout', 'discardResponseMessage']);\n  return Object.keys(p).every((k) => allowed.has(k));\n}","tryCatchPattern":null,"preventionTips":["Remember grpc uses metadata, not headers","Set metric names via tags: { name: '...' }, not a top-level name key","Keep separate param builders for http and grpc instead of one shared object"],"tags":["grpc","params","validation","typo"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}