JuliusBrussee/caveman · error

browser returned no result object

Error message

browser returned no result object

What it means

decodeBoolObject extracts a boolean from a CDP CallFunctionOn result RemoteObject. Chrome can legitimately return a nil RemoteObject without a transport error; blindly dereferencing res.Value would nil-panic and, through the MCP panic hole, crash the whole process (issue #140). The guard fails closed with 'browser returned no result object' instead.

Solutions

  1. Treat the returned error as a failed action and retry the call on a freshly resolved node
  2. Check browser/tab health (is the target still alive?) before re-running the evaluation
  3. Re-resolve the DOM node and re-dispatch the boolean call rather than reusing stale ids
  4. Keep the guard in place; never patch around it by dereferencing res unconditionally

Example fix

// before
ok, err := d.callBoolOnNode(ctx, staleTarget, fn) // nil result -> guarded error
// after
target, err := d.resolve(selector)
if err != nil { return err }
ok, err := d.callBoolOnNode(ctx, target, fn)
if err != nil { return retryOrReport(err) }
Defensive patterns

Strategy: try-catch

Type guard

func hasResultObject(res *runtime.RemoteObject) bool {
    return res != nil && len(res.Value) > 0
}

Try / catch

ok, err := d.callBoolOnNode(ctx, target, fn)
if err != nil {
    if err.Error() == "browser returned no result object" {
        // tab likely crashed/navigated: re-resolve node and retry once
        return d.re ResolveAndRetry(ctx, selector, fn)
    }
    return err
}

Prevention

When it happens

Trigger: callBoolOnNode invokes an injected function via CallFunctionOn and chromedp returns a nil *runtime.RemoteObject (no error); decodeBoolObject's res == nil branch raises. Covered by TestDecodeBoolObjectGuardsNilAndDecodes.

Common situations: Browser tab crashed or navigated mid-call so the evaluation result is lost; a JS exception path returns no value object; CDP connection degraded under load and the response omits the result; evaluating on a destroyed execution context.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/3d87e985a77c3c35. Report an issue: GitHub.

Appendix: source

Thrown at browse/cdp.go:386

	_, err := d.callOnNodeRaw(ctx, target, fn)
	return err
}

func (d *CDPDriver) callBoolOnNode(ctx context.Context, target Target, fn string) (bool, error) {
	res, err := d.callOnNodeRaw(ctx, target, fn)
	if err != nil {
		return false, err
	}
	return decodeBoolObject(res)
}

// decodeBoolObject reads a boolean out of a CallFunctionOn result, failing
// closed on a nil object. CallFunctionOn can return a nil RemoteObject without
// an error; dereferencing res.Value there would nil-panic and, through the MCP
// panic hole, kill the whole process (issue #140).
func decodeBoolObject(res *runtime.RemoteObject) (bool, error) {
	if res == nil {
		return false, errors.New("browser returned no result object")
	}
	var out bool
	if err := json.Unmarshal(res.Value, &out); err != nil {
		return false, err
	}
	return out, nil
}

func (d *CDPDriver) callOnNodeRaw(ctx context.Context, target Target, fn string) (*runtime.RemoteObject, error) {
	if target.BackendDOMNodeID <= 0 {
		return nil, errors.New("missing backend DOM node id")
	}
	var res *runtime.RemoteObject
	var exception *runtime.ExceptionDetails
	if err := chromedp.Run(ctx, chromedp.ActionFunc(func(actionCtx context.Context) error {
		obj, err := dom.ResolveNode().WithBackendNodeID(cdp.BackendNodeID(target.BackendDOMNodeID)).Do(actionCtx)
		if err != nil {
			return err

View on GitHub (pinned to 3ee70a1026)