{"record":{"id":"ace6d626207adc97","repo":"grafana/k6","slug":"cannot-add-tags-for-the-s-custom-metric-w","errorCode":null,"errorMessage":"cannot add tags for the '%s' custom metric: %w","messagePattern":"cannot add tags for the '(.+?)' custom metric: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/metrics/metrics.go","lineNumber":115,"sourceCode":"\t\treturn raiseErr(fmt.Errorf(\"no value was provided for metric '%s', a number or a boolean value is expected\",\n\t\t\tm.metric.Name))\n\t}\n\tif sobek.IsNull(v) {\n\t\treturn raiseNan()\n\t}\n\n\tvfloat := v.ToFloat()\n\tif vfloat == 0 && v.ToBoolean() {\n\t\tvfloat = 1.0\n\t}\n\n\tif math.IsNaN(vfloat) {\n\t\treturn raiseNan()\n\t}\n\n\tctm := state.Tags.GetCurrentValues()\n\tif err := common.ApplyCustomUserTags(m.vu.Runtime(), &ctm, addTags); err != nil {\n\t\treturn false, fmt.Errorf(\"cannot add tags for the '%s' custom metric: %w\", m.metric.Name, err)\n\t}\n\n\tsample := metrics.Sample{\n\t\tTimeSeries: metrics.TimeSeries{\n\t\t\tMetric: m.metric,\n\t\t\tTags:   ctm.Tags,\n\t\t},\n\t\tTime:     time.Now(),\n\t\tMetadata: ctm.Metadata,\n\t\tValue:    vfloat,\n\t}\n\tmetrics.PushIfNotDone(m.vu.Context(), state.Samples, sample)\n\treturn true, nil\n}\n\ntype (\n\t// RootModule is the root metrics module\n\tRootModule struct{}","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/metrics/metrics.go#L97-L133","documentation":"Thrown by k6 custom metrics when the optional tags argument to .add(value, tags) cannot be applied. After the value passes validation, Metric.add (internal/js/modules/k6/metrics/metrics.go:113-116) calls common.ApplyCustomUserTags with the tags argument; if it is not a plain object of string keys/values (e.g. a string, number, or array), the wrapped error is returned unconditionally — unlike value errors, this always errors regardless of the throw option.","triggerScenarios":"myCounter.add(1, 'error'); myTrend.add(5, 200); myGauge.add(3, ['a','b']); myRate.add(1, null-as-object). Passing a serialized JSON string instead of an object also fails the map conversion.","commonSituations":"Passing a status code number or a tag string directly instead of an object; reusing a response header string as tags; a helper signature mismatch where tags and value are swapped (add(tags, value)).","solutions":["Wrap tags in an object with string values: myCounter.add(1, { code: String(resp.status) }).","Check argument order — the signature is add(value, tags), not add(tags, value).","Omit the second argument entirely if no tags are needed: myCounter.add(1).","Coerce non-string values (numbers, booleans) inside the tags object to strings."],"exampleFix":"// before\nmyCounter.add(resp.status);            // value ok, but intended as tag\nmyTrend.add(5, resp.status);            // tags = number -> error\n\n// after\nmyTrend.add(5, { code: String(resp.status) });","handlingStrategy":"validation","validationCode":"function safeAdd(metric, value, tags) {\n  let t = tags;\n  if (t !== undefined && t !== null) {\n    if (typeof t !== 'object' || Array.isArray(t)) throw new Error('tags must be an object of string keys/values');\n    t = Object.fromEntries(Object.entries(t).map(([k, v]) => [k, String(v)]));\n  }\n  metric.add(value, t);\n}","typeGuard":"const isTagsObject = (t) => t == null || (typeof t === 'object' && !Array.isArray(t) && Object.values(t).every((v) => typeof v === 'string'));","tryCatchPattern":null,"preventionTips":["Keep the argument order add(value, tags) in mind; never pass primitives as tags.","Stringify numeric tag values (status codes, timings) at the call site."],"tags":["metrics","validation","tags","custom-metrics"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}