grafana/k6 · error

cannot add tags for the '%s' custom metric: %w

Error message

cannot add tags for the '%s' custom metric: %w

What it means

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.

Source

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

		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()
	}

	ctm := state.Tags.GetCurrentValues()
	if err := common.ApplyCustomUserTags(m.vu.Runtime(), &ctm, addTags); err != nil {
		return false, fmt.Errorf("cannot add tags for the '%s' custom metric: %w", m.metric.Name, err)
	}

	sample := metrics.Sample{
		TimeSeries: metrics.TimeSeries{
			Metric: m.metric,
			Tags:   ctm.Tags,
		},
		Time:     time.Now(),
		Metadata: ctm.Metadata,
		Value:    vfloat,
	}
	metrics.PushIfNotDone(m.vu.Context(), state.Samples, sample)
	return true, nil
}

type (
	// RootModule is the root metrics module
	RootModule struct{}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Wrap tags in an object with string values: myCounter.add(1, { code: String(resp.status) }).
  2. Check argument order — the signature is add(value, tags), not add(tags, value).
  3. Omit the second argument entirely if no tags are needed: myCounter.add(1).
  4. Coerce non-string values (numbers, booleans) inside the tags object to strings.

Example fix

// before
myCounter.add(resp.status);            // value ok, but intended as tag
myTrend.add(5, resp.status);            // tags = number -> error

// after
myTrend.add(5, { code: String(resp.status) });
Defensive patterns

Strategy: validation

Validate before calling

function safeAdd(metric, value, tags) {
  let t = tags;
  if (t !== undefined && t !== null) {
    if (typeof t !== 'object' || Array.isArray(t)) throw new Error('tags must be an object of string keys/values');
    t = Object.fromEntries(Object.entries(t).map(([k, v]) => [k, String(v)]));
  }
  metric.add(value, t);
}

Type guard

const isTagsObject = (t) => t == null || (typeof t === 'object' && !Array.isArray(t) && Object.values(t).every((v) => typeof v === 'string'));

Prevention

When it happens

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

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

Related errors


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