grafana/k6 · warning

'%s' is an invalid value for metric '%s', a number or a bool

Error message

'%s' is an invalid value for metric '%s', a number or a boolean value is expected

What it means

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).

Source

Thrown at internal/js/modules/k6/metrics/metrics.go:92

	}, string(omitMsg))
}

func (m Metric) add(v sobek.Value, addTags sobek.Value) (bool, error) {
	state := m.vu.State()
	if state == nil {
		return false, ErrMetricsAddInInitContext
	}

	// return/throw exception if throw enabled, otherwise just log
	raiseErr := func(err error) (bool, error) { //nolint:unparam // we want to just do `return raiseErr(...)`
		if state.Options.Throw.Bool {
			return false, err
		}
		state.Logger.Warn(err)
		return false, nil
	}
	raiseNan := func() (bool, error) {
		return raiseErr(fmt.Errorf("'%s' is an invalid value for metric '%s', a number or a boolean value is expected",
			limitValue(v.String()), m.metric.Name))
	}

	if v == nil {
		return raiseErr(fmt.Errorf("no value was provided for metric '%s', a number or a boolean value is expected",
			m.metric.Name))
	}
	if sobek.IsNull(v) {
		return raiseNan()
	}

	vfloat := v.ToFloat()
	if vfloat == 0 && v.ToBoolean() {
		vfloat = 1.0
	}

	if math.IsNaN(vfloat) {
		return raiseNan()

View on GitHub (pinned to 93accf6570)

Solutions

  1. 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.
  2. Sanitize before adding: Number.isFinite(Number(v)) ? v : 0 (or skip the sample).
  3. Coerce stringly-typed fields explicitly: myTrend.add(Number(resp.json('price'))).
  4. Guard optional fields: if (value !== null && value !== undefined) myGauge.add(value).

Example fix

// before
const latency = parseFloat(resp.headers['X-Latency']); // NaN when header missing
myTrend.add(latency);

// after
const latency = parseFloat(resp.headers['X-Latency']);
if (Number.isFinite(latency)) myTrend.add(latency);
Defensive patterns

Strategy: validation

Validate before calling

function safeAdd(metric, value, tags) {
  if (value === null || value === undefined) return false;
  if (typeof value === 'number' && !Number.isFinite(value)) return false;
  if (typeof value !== 'number' && typeof value !== 'boolean') {
    const n = Number(value);
    if (!Number.isFinite(n)) return false;
  }
  metric.add(value, tags);
  return true;
}

Type guard

const isMetricValue = (v) => typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v));

Try / catch

try { metric.add(value); } catch (e) { if (/is an invalid value for metric/.test(e.message)) { /* coerce or skip, log once */ } else throw e; }

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/eaedacdb01251f60. Report an issue: GitHub.