grafana/k6 · error

invalid metadata param: %w

Error message

invalid metadata param: %w

What it means

params.metadata on client.invoke() or new grpc.Stream() is parsed by newMetadata(): it must be a plain object mapping header names to values. Keys ending in -bin must map to binary values ([]byte from JS: Uint8Array/ArrayBuffer); all other keys must map to strings. A non-object value, or any value of the wrong type, fails and is rethrown wrapped as 'invalid metadata param'.

Source

Thrown at internal/js/modules/k6/grpc/params.go:47

func newCallParams(vu modules.VU, input sobek.Value) (*callParams, error) {
	result := &callParams{
		Metadata:    metadata.New(nil),
		TagsAndMeta: vu.State().Tags.GetCurrentValues(),
	}

	if common.IsNullish(input) {
		return result, nil
	}

	rt := vu.Runtime()
	params := input.ToObject(rt)

	for _, k := range params.Keys() {
		switch k {
		case "metadata":
			md, err := newMetadata(params.Get(k))
			if err != nil {
				return result, fmt.Errorf("invalid metadata param: %w", err)
			}

			result.Metadata = md
		case "tags":
			if err := common.ApplyCustomUserTags(rt, &result.TagsAndMeta, params.Get(k)); err != nil {
				return result, fmt.Errorf("metric tags: %w", err)
			}
		case "timeout":
			var err error
			v := params.Get(k).Export()
			result.Timeout, err = types.GetDurationValue(v)
			if err != nil {
				return result, fmt.Errorf("invalid timeout value: %w", err)
			}
		case "discardResponseMessage":
			result.DiscardResponseMessage = params.Get(k).ToBoolean()
		default:
			return result, fmt.Errorf("unknown param: %q", k)

View on GitHub (pinned to 93accf6570)

Solutions

  1. Pass a plain object: { authorization: 'token' }
  2. Give -bin keys Uint8Array values (e.g. new Uint8Array([2, 200]))
  3. Stringify non-string scalars: String(count)

Example fix

// before
client.invoke(method, req, { metadata: { 'x-load-tester-bin': 'buffer' } });

// after
client.invoke(method, req, { metadata: { 'x-load-tester-bin': new Uint8Array([2, 200]) } });
Defensive patterns

Strategy: type-guard

Validate before calling

assertGrpcMetadata(params.metadata);
client.invoke(method, req, params);

Type guard

function assertGrpcMetadata(obj, where = 'metadata') {
  if (obj == null) return true;
  if (typeof obj !== 'object' || Array.isArray(obj)) {
    throw new Error(`${where} must be a plain object of key-value pairs`);
  }
  for (const [k, v] of Object.entries(obj)) {
    if (k.endsWith('-bin')) {
      if (!(v instanceof Uint8Array) && !(v instanceof ArrayBuffer)) {
        throw new Error(`${where}["${k}"] must be binary (Uint8Array/ArrayBuffer)`);
      }
    } else if (typeof v !== 'string') {
      throw new Error(`${where}["${k}"] must be a string`);
    }
  }
  return true;
}

Prevention

When it happens

Trigger: metadata: 'authorization=token' (a string instead of an object); metadata: ['a', 'b'] (an array); { 'trace-id-bin': 'xyz' } (string for a -bin key); { count: 5 } (number for a normal key).

Common situations: Porting HTTP header strings or curl-style 'k: v' syntax; passing base64 strings where the server expects binary metadata; numbers and booleans not stringified before being spread into metadata.

Related errors


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