fix(browser): recover from CDP DOM-node serialization crash in browser_console (#35385)
browser_console(expression="document.body") returned the cryptic CDP error "Object reference chain is too long" instead of a usable result. With returnByValue=true, Chrome deep-serializes the eval result; for a live DOM Node/NodeList/Window that serialization overruns CDP's recursion guard and fails the whole call with a protocol-level error (not a JS exception), which _browser_eval surfaced raw. - browser_supervisor.evaluate_runtime: on that specific error, retry once with returnByValue=false so Chrome returns the node's description string — the same graceful path already used for document.querySelector() results. - browser_tool._browser_eval (CLI subprocess fallback): the subprocess can't retry, so convert the reference-chain error into actionable guidance (extract a primitive / use JSON.stringify) instead of leaking it raw. No expression rewriting — normal evals (1+41 -> 42) are untouched.
This commit is contained in:
@@ -496,12 +496,12 @@ class CDPSupervisor:
|
||||
if not session_id:
|
||||
return {"ok": False, "error": "supervisor has no attached page session"}
|
||||
|
||||
async def _do_eval() -> Dict[str, Any]:
|
||||
async def _do_eval(by_value: bool) -> Dict[str, Any]:
|
||||
return await self._cdp(
|
||||
"Runtime.evaluate",
|
||||
{
|
||||
"expression": expression,
|
||||
"returnByValue": return_by_value,
|
||||
"returnByValue": by_value,
|
||||
"awaitPromise": await_promise,
|
||||
# userGesture matters for things like clipboard / fullscreen
|
||||
# APIs that require a user-activation context.
|
||||
@@ -511,14 +511,32 @@ class CDPSupervisor:
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
try:
|
||||
from agent.async_utils import safe_schedule_threadsafe
|
||||
fut = safe_schedule_threadsafe(_do_eval(), loop)
|
||||
from agent.async_utils import safe_schedule_threadsafe
|
||||
|
||||
def _run_eval(by_value: bool) -> Dict[str, Any]:
|
||||
fut = safe_schedule_threadsafe(_do_eval(by_value), loop)
|
||||
if fut is None:
|
||||
return {"ok": False, "error": "Browser supervisor loop unavailable"}
|
||||
response = fut.result(timeout=timeout + 1)
|
||||
raise RuntimeError("Browser supervisor loop unavailable")
|
||||
return fut.result(timeout=timeout + 1)
|
||||
|
||||
try:
|
||||
response = _run_eval(return_by_value)
|
||||
except Exception as exc:
|
||||
return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
||||
# ``returnByValue=True`` asks Chrome to deep-serialize the result.
|
||||
# For live DOM nodes / NodeLists / Window that serialization can
|
||||
# blow past CDP's recursion guard and fail the whole call with
|
||||
# ``Object reference chain is too long`` (a protocol-level error,
|
||||
# not a JS exception). Retry once with ``returnByValue=False`` so
|
||||
# Chrome returns the object's description string instead — the same
|
||||
# graceful degradation path used for ``document.querySelector(...)``
|
||||
# results — rather than crashing the eval.
|
||||
if return_by_value and "reference chain is too long" in str(exc).lower():
|
||||
try:
|
||||
response = _run_eval(False)
|
||||
except Exception as exc2:
|
||||
return {"ok": False, "error": f"{type(exc2).__name__}: {exc2}"}
|
||||
else:
|
||||
return {"ok": False, "error": f"{type(exc).__name__}: {exc}"}
|
||||
|
||||
# Runtime.evaluate response shape:
|
||||
# {"id": N, "result": {"result": {"type": "...", "value": ..., ...},
|
||||
|
||||
@@ -2874,6 +2874,22 @@ def _browser_eval(expression: str, task_id: Optional[str] = None) -> str:
|
||||
"error": f"JavaScript evaluation is not supported by this browser backend. {err}",
|
||||
}
|
||||
return json.dumps(_copy_fallback_warning(response, result))
|
||||
# A live DOM node / NodeList / Window can't be JSON-serialized by CDP
|
||||
# and fails the eval with "Object reference chain is too long". The
|
||||
# supervisor fast path retries with returnByValue=false, but the CLI
|
||||
# subprocess can't, so turn the cryptic protocol error into actionable
|
||||
# guidance instead of surfacing it raw.
|
||||
if "reference chain is too long" in err.lower():
|
||||
response = {
|
||||
"success": False,
|
||||
"error": (
|
||||
"Expression returned a live DOM node / NodeList / Window, "
|
||||
"which can't be serialized. Extract a primitive value "
|
||||
"(e.g. .innerText, .href, .src, .value) or use "
|
||||
"JSON.stringify() / a snapshot tool instead."
|
||||
),
|
||||
}
|
||||
return json.dumps(_copy_fallback_warning(response, result))
|
||||
response = {
|
||||
"success": False,
|
||||
"error": err,
|
||||
|
||||
Reference in New Issue
Block a user