Skip to content

fix(computer_use): disable cua-driver overlay by default on macOS/WSL (supersedes #53841) - #69903

Merged
OutThisLife merged 8 commits into
mainfrom
bb/salvage-53841-no-overlay
Jul 23, 2026
Merged

fix(computer_use): disable cua-driver overlay by default on macOS/WSL (supersedes #53841)#69903
OutThisLife merged 8 commits into
mainfrom
bb/salvage-53841-no-overlay

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

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-overlay or hid the session cursor.

What this PR does

  • Add computer_use.no_overlay (None = auto, true/false override)
  • Auto-detect disables overlay on macOS, headless Linux, and WSL2; keeps it on Windows and desktop Linux with a display
  • Pass --no-overlay when the installed driver supports it (sanitized --help probe on the resolved command)
  • After start_session, call set_agent_cursor_enabled(false) when the policy is on (covers older drivers)
  • Address sweeper notes from fix(computer_use): pass --no-overlay to cua-driver on Linux/WSL2 to prevent idle CPU #53841 (sanitized env, manifest command for support check, no config-version bump)

What changed vs #53841

  • macOS defaults to overlay off (the original PR left it on)
  • Session-API cursor disable as a second kill switch

Set computer_use.no_overlay: false to keep the visual cursor.

Test plan

  • pytest tests/computer_use/test_cua_no_overlay.py -q → 21 passed
  • Manual macOS: after a computer_use click, cua-driver idle CPU near 0%
  • computer_use.no_overlay: false restores the overlay

Closes #28152
Closes #47032

Credit: @DavidMetcalfe (primary).

DavidMetcalfe and others added 6 commits July 23, 2026 00:44
…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>
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 69b97a9

all good!

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/tools Tool registry, model_tools, toolsets sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 23, 2026
@isak-ialogics

Copy link
Copy Markdown
Contributor

Triage evidence only (not a merge/close recommendation): the red Python slice is localized to tests/tools/test_computer_use.py::TestLazyMcpInstall::test_start_lazy_installs_mcp, not the new focused overlay suite. On headless Linux _cua_no_overlay() is true, so the new post-start_session set_agent_cursor_enabled(false) path runs; this test mocks McpSession.start() without marking the session started, causing call_tool() to auto-start it again and making mock_start report 2 calls instead of 1 (RuntimeError: cua-driver session not started follows). Concrete next step: isolate this lazy-install test from overlay policy (e.g. patch _cua_no_overlay false), or make its mocked session emulate the started state, then rerun scripts/run_tests.sh tests/tools/test_computer_use.py::TestLazyMcpInstall::test_start_lazy_installs_mcp. CI job: https://github.com/NousResearch/hermes-agent/actions/runs/29983029603/job/89128889427

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
@OutThisLife
OutThisLife merged commit 93f8da5 into main Jul 23, 2026
37 checks passed
@OutThisLife
OutThisLife deleted the bb/salvage-53841-no-overlay branch July 23, 2026 06:36
SchweppesSoda pushed a commit to SchweppesSoda/hermes-agent that referenced this pull request Jul 26, 2026
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.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…841-no-overlay

fix(computer_use): disable cua-driver overlay by default on macOS/WSL (supersedes NousResearch#53841)
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
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.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

4 participants