grafana/k6 · critical

error parsing script options: %w

Error message

error parsing script options: %w

What it means

After init code runs, k6 exports the script's `options` object and re-serializes it with encoding/json (json.MarshalIndent) to validate it against lib.Options. If the exported value contains something JSON cannot represent - functions, circular references, or unsupported exported Go values - the marshal fails and this error wraps the encoding/json cause. It is a script-authoring error: `options` must be a plain, JSON-serializable data object.

Source

Thrown at internal/js/bundle.go:205

	var err error
	ch := make(chan struct{})
	bi.mainModule.GetExportedNames(func(names []string) {
		defer close(ch)
		for _, k := range names {
			v := bi.getExported(k)
			if _, ok := sobek.AssertFunction(v); ok && k != consts.Options {
				b.callableExports[k] = struct{}{}
				continue
			}
			switch k {
			case consts.Options:
				if !updateOptions || v == nil {
					continue
				}
				var data []byte
				data, err = json.MarshalIndent(v.Export(), "", "  ")
				if err != nil {
					err = fmt.Errorf("error parsing script options: %w", err)
					return
				}
				dec := json.NewDecoder(bytes.NewReader(data))
				dec.DisallowUnknownFields()
				if err = dec.Decode(&b.Options); err != nil {
					if uerr := json.Unmarshal(data, &b.Options); uerr != nil {
						// Beautify the error so we can try to show the user the key and potential value that is failing to parse
						uerr = beautifyOptionsJSONUnmarshalError(data, uerr)
						err = errext.WithAbortReasonIfNone(
							errext.WithExitCodeIfNone(uerr, exitcodes.InvalidConfig),
							errext.AbortedByScriptError,
						)
						return
					}
					b.preInitState.Logger.WithError(err).Warn("There were unknown fields in the options exported in the script")
					err = nil
				}
			case consts.SetupFn:

View on GitHub (pinned to 93accf6570)

Solutions

  1. Strip every non-data value (functions, class instances, self-references) from the exported options object - keep only documented k6 options
  2. If options are computed, audit the merge sources for stray callables or cycles before export
  3. Move helpers and callbacks to plain module-level variables or separate exports

Example fix

// before: function inside options -> json.Marshal fails
export const options = {
  vus: 5,
  duration: '30s',
  helper: () => console.log('x'),
};

// after: only data in options; helper lives outside
const helper = () => console.log('x');
export const options = { vus: 5, duration: '30s' };
Defensive patterns

Strategy: validation

Validate before calling

// self-check options before export: catches circular structures (functions are dropped silently by stringify)
try {
  JSON.stringify(options);
} catch (e) {
  throw new Error(`exported options are not JSON-serializable: ${e.message}`);
}
const hasFn = Object.values(options).some((v) => typeof v === 'function');
if (hasFn) throw new Error('exported options must not contain functions');
export const options = opts;

Prevention

When it happens

Trigger: Exporting options that contain a function (e.g. a helper or callback accidentally merged in), a self-referencing/circular object, or a value whose Sobek export maps to a Go type json.Marshal rejects (func, chan, complex). Example: export const options = { vus: 2, helper: () => 42 }.

Common situations: Building options programmatically and merging in extra properties; copy-pasting config between files and dragging along callbacks; storing class instances or shared objects inside options.

Related errors


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