grafana/k6 · error

unmarshalling options for SDK: %w

Error message

unmarshalling options for SDK: %w

What it means

Client.StartLocalExecution (internal/cloudapi/provisioning/api.go:201) adapts the caller-supplied req.Options (json.RawMessage of pre-marshalled lib.Options) into map[string]any for the generated OpenAPI SDK. This error means that payload is not valid JSON, so the request can never be built.

Source

Thrown at internal/cloudapi/provisioning/api.go:201

	}
}

// StartLocalExecution starts a local-execution test run via the
// provisioning API. It generates a K6-Idempotency-Key header for
// safe retries. The caller provides options as pre-marshalled JSON.
func (c *Client) StartLocalExecution(
	ctx context.Context, loadTestID int64, req StartLocalExecutionRequest,
) (*StartLocalExecutionResponse, error) {
	// Generate idempotency key: 8 random bytes hex-encoded (16 chars).
	var key [8]byte
	if _, err := rand.Read(key[:]); err != nil {
		return nil, fmt.Errorf("generating idempotency key: %w", err)
	}

	// SDK adapter: unmarshal json.RawMessage → map[string]any.
	var opts map[string]any
	if err := json.Unmarshal(req.Options, &opts); err != nil {
		return nil, fmt.Errorf("unmarshalling options for SDK: %w", err)
	}

	maxVUs, err := toInt32(req.MaxVUs)
	if err != nil {
		return nil, fmt.Errorf("max_vus: %w", err)
	}
	totalDuration, err := toInt32(req.TotalDuration)
	if err != nil {
		return nil, fmt.Errorf("total_duration: %w", err)
	}

	sdkReq := k6cloud.NewStartLocalExecutionTestRequest(opts, maxVUs, totalDuration)

	if req.ArchiveSize > 0 {
		v, err := toInt32(req.ArchiveSize)
		if err != nil {
			return nil, fmt.Errorf("archive_size: %w", err)
		}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Pass Options verbatim from json.Marshal(opts) of lib.Options
  2. Pre-validate with json.Valid(req.Options) before calling StartLocalExecution
  3. Log the offending payload (truncated) when the error fires to find the producer
  4. If it comes from k6 itself rather than custom code, report a k6 bug

Example fix

// before
req := provisioning.StartLocalExecutionRequest{
	Options: json.RawMessage(`{"vus":`), // truncated / invalid JSON
}

// after
b, err := json.Marshal(libOpts)
if err != nil {
	return err
}
req := provisioning.StartLocalExecutionRequest{Options: b}
Defensive patterns

Strategy: validation

Validate before calling

if !json.Valid(req.Options) {
	return fmt.Errorf("options payload is not valid JSON (%d bytes)", len(req.Options))
}

Type guard

func isValidOptionsJSON(raw json.RawMessage) bool {
	return json.Valid(raw)
}

Try / catch

if err := client.StartLocalExecution(ctx, id, req); err != nil {
	if strings.Contains(err.Error(), "unmarshalling options") {
		log.Printf("options payload corrupt: %q", truncate(req.Options, 256))
	}
	return err
}

Prevention

When it happens

Trigger: StartLocalExecutionRequest.Options populated with hand-built, truncated, or empty bytes instead of the output of json.Marshal(lib.Options); upstream marshalling produced corrupt output; a test fixture contains malformed JSON.

Common situations: Custom code paths or tests that construct StartLocalExecutionRequest directly instead of going through cmd's marshalling of lib.Options; version skew where the Options marshalling format changed unexpectedly.

Related errors


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