{"record":{"id":"2c0826fe3c5186f8","repo":"grafana/k6","slug":"invalid-websocket-tags-option-w","errorCode":null,"errorMessage":"invalid WebSocket tags option: %w","messagePattern":"invalid WebSocket tags option: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/websockets/params.go","lineNumber":59,"sourceCode":"\n\tparams := raw.ToObject(rt)\n\tfor _, k := range params.Keys() {\n\t\tswitch k {\n\t\tcase \"headers\":\n\t\t\theadersV := params.Get(k)\n\t\t\tif common.IsNullish(headersV) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\theadersObj := headersV.ToObject(rt)\n\t\t\tif headersObj == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, key := range headersObj.Keys() {\n\t\t\t\tparsed.headers.Set(key, headersObj.Get(key).String())\n\t\t\t}\n\t\tcase \"tags\":\n\t\t\tif err := common.ApplyCustomUserTags(rt, parsed.tagsAndMeta, params.Get(k)); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid WebSocket tags option: %w\", err)\n\t\t\t}\n\t\tcase \"jar\":\n\t\t\tjarV := params.Get(k)\n\t\t\tif common.IsNullish(jarV) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif v, ok := jarV.Export().(*httpModule.CookieJar); ok {\n\t\t\t\tparsed.cookieJar = v.Jar\n\t\t\t}\n\t\tcase \"compression\":\n\t\t\t// deflate compression algorithm is supported - as defined in RFC7692\n\t\t\t// compression here relies on the implementation in gorilla/websocket package, usage is\n\t\t\t// experimental and may result in decreased performance. package supports\n\t\t\t// only \"no context takeover\" scenario\n\n\t\t\talgoString := strings.TrimSpace(params.Get(k).ToString().String())\n\t\t\tif algoString != \"deflate\" {\n\t\t\t\treturn nil, fmt.Errorf(\"unsupported compression algorithm '%s', supported algorithm is 'deflate'\", algoString)","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/websockets/params.go#L41-L77","documentation":"new WebSocket(url, { tags: {...} }) validates each tag through common.ApplyCustomUserTags before connecting. If a tag value is not a string, boolean, or number (e.g. an object, array, or nested structure), the apply fails and k6 wraps it as 'invalid WebSocket tags option'. Only flat, primitive-valued tags are representable in k6's metric engine.","triggerScenarios":"new WebSocket(url, { tags: { tenant: { id: 7 } } }), tags: { list: ['a','b'] }, or any tags object whose export is a map containing non-primitive values. The headers/compression cases in the same parse loop succeed; only the tags branch produces this message.","commonSituations":"Reusing an options object built for HTTP requests where a tag value happens to be an object; spreading a config object into tags and accidentally including nested keys; migrating scripts where tags were previously silently coerced; putting the URL or params object itself into tags.","solutions":["Flatten every tag value to a primitive: tenant: String(obj.id) or JSON.stringify for complex values","Whitelist only the scalar keys you need when spreading: { tags: { name: cfg.name, env: cfg.env } } where both are strings/numbers","Sanitize with a helper that drops or stringifies non-primitives before constructing the WebSocket","Check the wrapped cause text — it names the offending value/type"],"exampleFix":"// before\nconst ws = new WebSocket(url, { tags: { user: userObj } }); // userObj is {id:1} -> invalid\n\n// after\nconst ws = new WebSocket(url, { tags: { user: String(userObj.id) } });","handlingStrategy":"validation","validationCode":"function sanitizeTags(tags) {\n  const out = {};\n  for (const [k, v] of Object.entries(tags || {})) {\n    if (v == null) continue;\n    const t = typeof v;\n    if (t === 'string' || t === 'number' || t === 'boolean') out[k] = v;\n    else out[k] = JSON.stringify(v);\n  }\n  return out;\n}\nconst sock = new WebSocket(url, { tags: sanitizeTags(cfgTags) });","typeGuard":"const isPrimitiveTag = v => v == null ? false : ['string','number','boolean'].includes(typeof v);","tryCatchPattern":"try { new WebSocket(url, { tags }); } catch (e) { if (/tags option/.test(e.message)) throw new Error(`tag values must be string/number/boolean: ${e.message}`); throw e; }","preventionTips":["Keep WebSocket tags flat and scalar; stringify anything structured","Beware low cardinality: stringified JSON tags multiply metric series","Lint your options object once at script start with the sanitize helper","Remember tag rules match the HTTP APIs — reuse the same sanitize code"],"tags":["websockets","tags","options","type-error"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}