{"record":{"id":"eaedacdb01251f60","repo":"grafana/k6","slug":"s-is-an-invalid-value-for-metric-s-a-number","errorCode":null,"errorMessage":"'%s' is an invalid value for metric '%s', a number or a boolean value is expected","messagePattern":"'(.+?)' is an invalid value for metric '(.+?)', a number or a boolean value is expected","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/js/modules/k6/metrics/metrics.go","lineNumber":92,"sourceCode":"\t}, string(omitMsg))\n}\n\nfunc (m Metric) add(v sobek.Value, addTags sobek.Value) (bool, error) {\n\tstate := m.vu.State()\n\tif state == nil {\n\t\treturn false, ErrMetricsAddInInitContext\n\t}\n\n\t// return/throw exception if throw enabled, otherwise just log\n\traiseErr := func(err error) (bool, error) { //nolint:unparam // we want to just do `return raiseErr(...)`\n\t\tif state.Options.Throw.Bool {\n\t\t\treturn false, err\n\t\t}\n\t\tstate.Logger.Warn(err)\n\t\treturn false, nil\n\t}\n\traiseNan := func() (bool, error) {\n\t\treturn raiseErr(fmt.Errorf(\"'%s' is an invalid value for metric '%s', a number or a boolean value is expected\",\n\t\t\tlimitValue(v.String()), m.metric.Name))\n\t}\n\n\tif v == nil {\n\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()","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/metrics/metrics.go#L74-L110","documentation":"Produced by k6 custom metrics (Counter/Gauge/Trend/Rate .add()) when the value argument cannot be interpreted as a number. In Metric.add (internal/js/modules/k6/metrics/metrics.go:91-111) this fires for sobek null values and for values whose ToFloat() is NaN (e.g. non-numeric strings, NaN, undefined coaxed to string). Behavior depends on the throw option: with --throw (or options.throw=true) it raises as an exception; otherwise it is only logged as a warning and the sample is DROPPED (add returns false).","triggerScenarios":"myCounter.add('abc'); myTrend.add(NaN); myRate.add(null); myGauge.add({v: 1}) (object coerced to NaN string); myTrend.add('12px') — strings that are not pure numeric literals fail v.ToFloat(). Booleans are fine (true becomes 1.0, false 0 via the ToBoolean branch).","commonSituations":"Feeding parseFloat results without checking NaN (e.g. parsing a response header that is sometimes missing); passing JSON fields that are strings in one response and numbers in another; null from optional fields; the silent warning variant hides the problem until results show missing samples.","solutions":["If the message appears as a warning: run once with --throw to turn it into a failing exception with a stack trace pointing at the exact add() call.","Sanitize before adding: Number.isFinite(Number(v)) ? v : 0 (or skip the sample).","Coerce stringly-typed fields explicitly: myTrend.add(Number(resp.json('price'))).","Guard optional fields: if (value !== null && value !== undefined) myGauge.add(value)."],"exampleFix":"// before\nconst latency = parseFloat(resp.headers['X-Latency']); // NaN when header missing\nmyTrend.add(latency);\n\n// after\nconst latency = parseFloat(resp.headers['X-Latency']);\nif (Number.isFinite(latency)) myTrend.add(latency);","handlingStrategy":"validation","validationCode":"function safeAdd(metric, value, tags) {\n  if (value === null || value === undefined) return false;\n  if (typeof value === 'number' && !Number.isFinite(value)) return false;\n  if (typeof value !== 'number' && typeof value !== 'boolean') {\n    const n = Number(value);\n    if (!Number.isFinite(n)) return false;\n  }\n  metric.add(value, tags);\n  return true;\n}","typeGuard":"const isMetricValue = (v) => typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v));","tryCatchPattern":"try { metric.add(value); } catch (e) { if (/is an invalid value for metric/.test(e.message)) { /* coerce or skip, log once */ } else throw e; }","preventionTips":["Run scripts with --throw during development to surface dropped samples as errors.","Number.isFinite(Number(x)) guard before adding parsed/untyped values.","Log a single diagnostic when a sample is skipped so silent data loss is visible in results."],"tags":["metrics","validation","custom-metrics","nan"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}