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
- Treat the returned error as a failed action and retry the call on a freshly resolved node
- Check browser/tab health (is the target still alive?) before re-running the evaluation
- Re-resolve the DOM node and re-dispatch the boolean call rather than reusing stale ids
- 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
- Never dereference CallFunctionOn results without a nil check — it can be nil without error
- Keep tab-health checks before evaluation batches; detect navigations/crashes early
- Fail closed: treat missing result objects as errors, not false
- Reuse the shared decodeBoolObject helper instead of hand-rolling result decoding
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
- box model has no quad
- missing backend DOM node id
- select: missing option
- browser function returned no result object
- browser function threw
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 errView on GitHub (pinned to 3ee70a1026)