GopeedLab/gopeed · error

execute expects a string or function, got %T (%q)

Error message

execute expects a string or function, got %T (%q)

What it means

The last-resort branch of NormalizeExecutableValue: the value is a goja.Value that is neither a string nor a callable object, and its String() rendering does not look like function source (looksLikeFunctionSource). Both the Go type and the stringified value are reported, so the message shows exactly what was passed — commonly a plain object rendering as "[object Object]".

Source

Thrown at pkg/download/engine/webview/runtime.go:594

	switch raw := value.Export().(type) {
	case string:
		return raw, nil
	}
	obj, ok := value.(*goja.Object)
	if ok {
		if _, ok := goja.AssertFunction(obj); ok {
			source, err := functionSource(obj)
			if err != nil {
				return "", err
			}
			return normalizeFunctionSource(source), nil
		}
	}
	source := strings.TrimSpace(value.String())
	if looksLikeFunctionSource(source) {
		return normalizeFunctionSource(source), nil
	}
	return "", fmt.Errorf("execute expects a string or function, got %T (%q)", value, source)
}

func normalizeFunctionSource(source string) string {
	repaired := repairArrowObjectLiteralSource(source)
	return "(" + repaired + ")"
}

func functionSource(fn *goja.Object) (string, error) {
	toStringValue := fn.Get("toString")
	toString, ok := goja.AssertFunction(toStringValue)
	if !ok {
		return "", fmt.Errorf("function.toString is not callable")
	}
	value, err := toString(fn)
	if err != nil {
		return "", err
	}
	return value.String(), nil

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Wrap the intent in a function: page.execute(() => ({selector: '#x'}))
  2. Pass expression source as a plain string: page.execute('document.title')
  3. Inspect the (%q) part of the message to see what the value actually stringified to, then fix the call site

Example fix

// before
await page.execute({ selector: '#x' }); // got *goja.Object ("[object Object]")
// after
await page.execute(() => document.querySelector('#x') !== null);
Defensive patterns

Strategy: type-guard

Type guard

// JS: narrow to string/function before execute (objects like element handles fail here)
function isExecutable(x) {
  if (typeof x === 'string' || typeof x === 'function') return true;
  return false; // numbers, booleans, arrays, plain objects, DOM nodes all fail
}

Prevention

When it happens

Trigger: page.execute({selector: '#x'}) or page.execute(123n)/page.execute(true) from JS; passing a DOM element, array, or serialized handle instead of a function; passing an object whose toString was overridden to non-function text.

Common situations: Confusing execute(page.waitForSelector(...)-style handle objects) with source; passing a parsed JSON config where a callback source string was expected; arrow functions already stringified but wrapped in extra quotes (a string containing quotes would take the string branch, so this is typically a genuine non-function object).

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/18df15a36fa700b7. Report an issue: GitHub.