{"record":{"id":"3d87e985a77c3c35","repo":"JuliusBrussee/caveman","slug":"browser-returned-no-result-object","errorCode":null,"errorMessage":"browser returned no result object","messagePattern":"browser returned no result object","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"browse/cdp.go","lineNumber":386,"sourceCode":"\t_, err := d.callOnNodeRaw(ctx, target, fn)\n\treturn err\n}\n\nfunc (d *CDPDriver) callBoolOnNode(ctx context.Context, target Target, fn string) (bool, error) {\n\tres, err := d.callOnNodeRaw(ctx, target, fn)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn decodeBoolObject(res)\n}\n\n// decodeBoolObject reads a boolean out of a CallFunctionOn result, failing\n// closed on a nil object. CallFunctionOn can return a nil RemoteObject without\n// an error; dereferencing res.Value there would nil-panic and, through the MCP\n// panic hole, kill the whole process (issue #140).\nfunc decodeBoolObject(res *runtime.RemoteObject) (bool, error) {\n\tif res == nil {\n\t\treturn false, errors.New(\"browser returned no result object\")\n\t}\n\tvar out bool\n\tif err := json.Unmarshal(res.Value, &out); err != nil {\n\t\treturn false, err\n\t}\n\treturn out, nil\n}\n\nfunc (d *CDPDriver) callOnNodeRaw(ctx context.Context, target Target, fn string) (*runtime.RemoteObject, error) {\n\tif target.BackendDOMNodeID <= 0 {\n\t\treturn nil, errors.New(\"missing backend DOM node id\")\n\t}\n\tvar res *runtime.RemoteObject\n\tvar exception *runtime.ExceptionDetails\n\tif err := chromedp.Run(ctx, chromedp.ActionFunc(func(actionCtx context.Context) error {\n\t\tobj, err := dom.ResolveNode().WithBackendNodeID(cdp.BackendNodeID(target.BackendDOMNodeID)).Do(actionCtx)\n\t\tif err != nil {\n\t\t\treturn err","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/browse/cdp.go#L368-L404","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nok, err := d.callBoolOnNode(ctx, staleTarget, fn) // nil result -> guarded error\n// after\ntarget, err := d.resolve(selector)\nif err != nil { return err }\nok, err := d.callBoolOnNode(ctx, target, fn)\nif err != nil { return retryOrReport(err) }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func hasResultObject(res *runtime.RemoteObject) bool {\n    return res != nil && len(res.Value) > 0\n}","tryCatchPattern":"ok, err := d.callBoolOnNode(ctx, target, fn)\nif err != nil {\n    if err.Error() == \"browser returned no result object\" {\n        // tab likely crashed/navigated: re-resolve node and retry once\n        return d.re ResolveAndRetry(ctx, selector, fn)\n    }\n    return err\n}","preventionTips":["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"],"tags":["browser-automation","cdp","nil-guard","defensive-programming"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}