CLI 3.0 harness: Input.dispatchMouseEvent/key events silently dropped in a long-lived session while Runtime.evaluate stays healthy (stale Input session; ensure_real_tab doesn't recover)

Author: KyzcreigCreated Aug 14, 2026Updated Sep 14, 2026

Summary

During an automated A/B benchmark driving the CLI 3.0 harness (via Hermes Agent's browser_exec integration, browser-use 0.1.8, macOS 26.6 / arm64, local Chromium), one session got into a state where click_at_xy() and non-printable press_key() events were silently droppedInput.dispatchMouseEvent completed without error but no events ever reached the document. The state persisted for the remainder of that session (~25 further executions) and cost the driving agent a long forensic workaround. I could not reproduce it deterministically afterwards; filing with the full evidence in case the sequence rings a bell.

Evidence from the incident session (agent-authored, captured in our tool-call log)

The driving agent proved event loss with a capture-phase listener rather than assuming:

  1. js("window.__hits=[]; document.addEventListener('click', e => window.__hits.push(...), true)")
  2. click_at_xy(x, y) at correct CSS coords (verified via getBoundingClientRect), then again at devicePixelRatio-scaled coords
  3. window.__hits remained [] in both cases — zero events delivered
  4. Keyboard fallback: press_key("space"), press_key("Enter"), arrows on a focused radio input — also no effect
  5. ensure_real_tab() was called and did not clear the state
  6. Manual re-attach attempt — cdp("Target.attachToTarget", targetId=..., flatten=True) then passing the fresh sessionId — hung the IPC socket (call never returned; daemon still daemon_alive() == True afterwards)
  7. What DID work: DOM-level el.click() and el.focus() + type_text() (i.e. Input.insertText), which the agent used to complete the form; the final server echo confirmed real values, so JS-evaluate paths were healthy while Input dispatch paths were not.

So within one daemon: Runtime.evaluate routed to the correct page the whole time, while Input.dispatchMouseEvent / raw key events went somewhere that wasn't the visible document — consistent with Input dispatch running against a stale/wrong CDP session while JS evaluation used a live one.

Possibly-relevant session prelude

The first execution of the incident session was model-authored Playwright-JS fed to the Python harness:

const page = await context.newPage();

which crashed the harness run with SyntaxError (in browser_harness/run.py:387 exec(code, globals())) — i.e. the daemon's first spawn/attach happened under an immediately-aborted exec. All subsequent executions reused that daemon (session: "httpbin"). The click/key deadness was discovered ~12 executions later, after normal goto_url navigation and several successful js()/fill_input() calls.

Repro attempts (all negative)

Same machine, same 0.1.8 install, immediately after:

  • fresh session, new_tab() → capture-listener + click_at_xy → event delivered, radio checked ✅
  • fresh session, goto_url() as first navigation (matching the incident) → ✅
  • direct CLI heredoc (no integration layer) → ✅
  • replaying the SyntaxError-first prelude in a fresh named session, then clicking → not yet re-triggered

Ask

  • Should Input.* dispatch in the daemon pin to the same session resolution as Runtime.evaluate, so the two can't diverge? (daemon.py resolves req.get("session_id") or self.session for both, but the incident shows they can end up effectively split.)
  • ensure_real_tab() appears intended as the recovery path — in this state it returned successfully yet did not restore Input delivery; a health check that round-trips a synthetic click against a listener would make recovery verifiable.
  • The explicit-session escape hatch (cdp(..., session_id=...)) hanging the IPC socket turns the last-resort recovery into a lockup; a timeout there would keep sessions debuggable.

Happy to provide the full tool-call sequence (28 executions) from our logs if useful.