js() runner silently returns empty results for entire session (Runtime.evaluate detach?) — undetectable dead state
Symptom
The js() runner (Runtime.evaluate wrapper) intermittently returns empty results for the whole session — not errors, just empty — for trivially valid expressions:
js("return 'pong'")→""js("(() => 1+1)()")→""
No exception, exit code 0, no diagnostic. Meanwhile page_info() / accessibility-tree reads and even cdp() calls keep working in the same failing session. The next session with identical code works fine. Observed ratio: 2 dead / 2 healthy across 4 sessions on one machine, one install.
Impact
Agents cannot distinguish "runner is dead" from "the page is blocking me". Real cost from last week:
- Several hours spent debugging a site that was never the problem (login-flow conclusion was wrong: the session was fine, the js() lane was dead).
- Worse false positive: a synthetic
.click()appeared to mutate app state (React removed a card from the DOM) while a fetch/XHR interceptor recorded zero network calls — a read-back via another lane showed nothing had actually happened. Had js() been dead only for clicks and alive for reads (or vice versa), reports of "mutation succeeded" would have been fabricated downstream.
Everything that routes through js() — in-page fetch(...) for authenticated reads, .click(), attribute reads — is nondeterministic at session granularity.
Environment
- browser-use CLI installed via uv into
/opt/data/home/.cache/uv/archive-v0/...(harnessbrowser_harness), Linux aarch64 (DGX host), driven by an agent over one persistent session. - Failing/edge tracebacks reference:
browser_use/cli.py:391 _dispatchbrowser_harness/run.py:168 (wrapper) / 201 _run_browser_harness / 406 _runbrowser_harness/helpers.py:571 js -> :587 _js_evaluate -> runtime.py:140/127 _runtime_value
- The error path (
RuntimeError: JavaScript evaluation failed ...; expression: ...) works correctly — this report is about the silent empty-return path, which has no signal at all.
Repro
- Fresh session,
new_tab(url)any page,wait_for_load(). js("return 'pong'").- Dead state: returns
""and stays dead for the session's entire lifetime; restart session sometimes healthy. There is no--doctorcheck or state flag that tells you which state you are in before you make your first real call.
Suggested fixes
- Deterministic health probe: make the empty result distinguishable from a legit empty/undefined evaluation (sentinel or exception on Runtime-context loss), and fold a
js("1+1")sanity eval intobrowser-use --doctor. - Root-cause the detach: empty-evals smell like
Runtime.evaluaterunning against a stale/detached or wrong-frame execution context afternew_tab/top-navigation while the return channel still reports 200. Suspect context re-attachment on the session-id used by_runtime_evaluate. - Surface which execution-context-id each js() call used (debug log), so the detach is visible in tracebacks.
Interim workaround (agents should adopt regardless)
Probe once per session before trusting any js-driven flow: js("(() => 1+1)()"); if empty, fall back to HTML-shell parsing / another automation lane, and never report mutation success from DOM state alone — always re-read server-side.
Related observations (likely not CLI bugs — noted for completeness)
- Cross-tab auth continuity: completing an SSO login in tab A then
new_tab(url)in tab B can lose the session cookie; single-tabgoto_urlthroughout is the reliable pattern. A doc note would save agents from misreading this as bot-blocking. - Some React handlers ignore
dispatchEvent-synthesized clicks but honor.click()-generated ones — expected web semantics; a FAQ line could prevent false "site is hostile" conclusions.
Source: browser-use/browser-use