fix(computer_use): disable cua-driver overlay by default on macOS/WSL (supersedes #53841) - #69903
Conversation
…revent idle CPU cua-driver's cursor overlay rendering loop can consume CPU indefinitely when idle (#28152, #47032). On Linux/WSL2, the overlay serves no visual purpose and the rendering path is the primary source of idle CPU usage. Add computer_use.no_overlay config option (default: auto-detect) that passes --no-overlay to cua-driver when enabled. Auto-detection disables the overlay on Linux (covers WSL2, headless, containers) where it has no benefit, and keeps it enabled on macOS/Windows where it is visually useful. Refs: #28152, #47032
…rm-dependent assertions - Add autouse fixture to TestMcpInvocationResolution to disable --no-overlay flag so existing tests assert baseline args - Make test_config_load_failure_fails_safe and test_missing_section_enables platform-aware (Linux auto-detect returns True, macOS/Windows False)
…er version probe Address review feedback from cross-vendor review (Flash + GPT-OSS): 1. Auto-detect now checks for headless Linux (no DISPLAY), WSL2 (/proc/version contains 'microsoft'), instead of all Linux. Desktop Linux with a compositor keeps the overlay. 2. Add _cua_driver_supports_no_overlay() that probes cua-driver --help to check if the flag is supported. Older drivers (< 0.6.x) reject unknown flags, so passing --no-overlay would crash the MCP spawn. 3. Update tests to cover headless vs desktop Linux, WSL2 detection, version probe, and the unsupported-driver fallback path.
…s + manifest probe
The hermes-sweeper review #4701565902 (2026-07-15) flagged two
consistency issues in `_cua_driver_supports_no_overlay` and one
additive-config concern:
1. `cua-backend.py:260` — the `cua-driver --help` support probe
inherited the full parent environment. cua-driver is a third-party
binary; every other spawn site in this file (manifest probe at
`:214`, MCP spawn at `:697`, install probe at `:997`) uses
`_sanitize_subprocess_env(cua_driver_child_env())`. The `--help`
probe should match. This was a low-impact leak (only help output
exits), but inconsistency is the wrong default for a third-party
subprocess.
2. `cua_backend.py:238` — when the manifest returned a `command`
different from the input `driver_cmd` parameter (e.g. a relocated
executable at `/opt/relocated/cua-driver` while the system binary
is at `/usr/bin/cua-driver`), the support probe ran against
`_CUA_DRIVER_CMD` (the default) instead of the manifest-discovered
`command`. Two failure modes:
- The wrapper binary supports `--no-overlay` but the system binary
doesn't → probe returns False → overlay kept despite capability.
- The system binary supports `--no-overlay` but the wrapper doesn't
→ probe returns True → MCP spawn crashes on the unknown flag.
3. The original commit bumped `_config_version` 31→32 for an additive
default (`computer_use.no_overlay: None`). AGENTS.md specifies that
additive defaults in existing sections are handled by deep merge
and should NOT trigger a version bump. After cherry-picking onto
current `origin/main` (which is already at 33), the bump is
effectively dropped — resolved to main's 33.
Changes:
- Add `env=_sanitize_subprocess_env(cua_driver_child_env())` to the
`--help` subprocess (with the same import + rationale comment as
the manifest probe).
- Pass `driver_cmd=command` (or `driver_cmd=driver_cmd` for the
fallback path) into `_mcp_args_with_overlay_flag`, so the support
probe runs against the binary that will actually be launched.
Tests (3 new):
- `test_help_probe_passes_sanitized_env` — verifies `subprocess.run`
is called with an `env=` kwarg.
- `test_manifest_command_drives_support_probe` — verifies the probe
runs against the manifest command when it differs from the input
driver_cmd.
- `test_fallback_uses_input_driver_cmd_for_support_probe` — verifies
the fallback path (no command in manifest) uses the input
driver_cmd.
- `test_probe_distinguishes_support_between_binaries` — sanity check
that the lru_cache key on `driver_cmd` prevents cross-binary
cache leakage.
File-revert negative test confirmed all three of the new
"manifest/probe" tests are load-bearing: with the pre-fix code, they
fail (probe runs against the default binary instead of the resolved
one); with the fix, they pass. 20/20 tests in
`tests/computer_use/test_cua_no_overlay.py` green.
`TestMcpInvocationResolution` (8/8) still green.
Refs: sweeper review #4701565902
Auto-detect now disables the cursor overlay on darwin as well as headless/WSL2 Linux. After start_session, also call set_agent_cursor_enabled(false) when the policy is on so older drivers without --no-overlay still tear the overlay down. Co-authored-by: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com>
૮ >ﻌ< ა ci reviewran on 69b97a9 all good! |
|
Triage evidence only (not a merge/close recommendation): the red Python slice is localized to |
Guard the post-start set_agent_cursor_enabled on _session._started so call_tool cannot re-enter session.start() (matches the start_session lifecycle guard).
…verlay # Conflicts: # tools/computer_use/cua_backend.py
CuaDriverBackend caches a long-lived cua-driver subprocess for the life of the Hermes process, and stop() was never called from anywhere — the driver outlived the session that spawned it. NousResearch#69903 stopped the orphan from pegging a core by disabling the cursor overlay, but left the process behind; this is item 3 of NousResearch#28152 ("Hermes does not keep the driver alive after tool completion"). Register an atexit hook, mirroring browser_tool's atexit.register(_emergency_cleanup_all_sessions). atexit only, no signal handlers, for the prompt_toolkit reason documented there. reset_backend_for_tests now reuses the same teardown instead of repeating it.
…841-no-overlay fix(computer_use): disable cua-driver overlay by default on macOS/WSL (supersedes NousResearch#53841)
CuaDriverBackend caches a long-lived cua-driver subprocess for the life of the Hermes process, and stop() was never called from anywhere — the driver outlived the session that spawned it. NousResearch#69903 stopped the orphan from pegging a core by disabling the cursor overlay, but left the process behind; this is item 3 of NousResearch#28152 ("Hermes does not keep the driver alive after tool completion"). Register an atexit hook, mirroring browser_tool's atexit.register(_emergency_cleanup_all_sessions). atexit only, no signal handlers, for the prompt_toolkit reason documented there. reset_backend_for_tests now reuses the same teardown instead of repeating it.
CuaDriverBackend caches a long-lived cua-driver subprocess for the life of the Hermes process, and stop() was never called from anywhere — the driver outlived the session that spawned it. NousResearch#69903 stopped the orphan from pegging a core by disabling the cursor overlay, but left the process behind; this is item 3 of NousResearch#28152 ("Hermes does not keep the driver alive after tool completion"). Register an atexit hook, mirroring browser_tool's atexit.register(_emergency_cleanup_all_sessions). atexit only, no signal handlers, for the prompt_toolkit reason documented there. reset_backend_for_tests now reuses the same teardown instead of repeating it.
Summary
Supersedes #53841.
cua-driver's cursor overlay can peg a CPU core while idle — confirmed on macOS via the vImage redraw loop (#47032) and reported on Linux/WSL2 (#28152). Hermes never passed
--no-overlayor hid the session cursor.What this PR does
computer_use.no_overlay(None= auto,true/falseoverride)--no-overlaywhen the installed driver supports it (sanitized--helpprobe on the resolved command)start_session, callset_agent_cursor_enabled(false)when the policy is on (covers older drivers)What changed vs #53841
Set
computer_use.no_overlay: falseto keep the visual cursor.Test plan
pytest tests/computer_use/test_cua_no_overlay.py -q→ 21 passedcua-driveridle CPU near 0%computer_use.no_overlay: falserestores the overlayCloses #28152
Closes #47032
Credit: @DavidMetcalfe (primary).