Skip to content

fix(cua-driver-rs)(macos): run the agent-cursor overlay in the serve daemon - #1788

Closed
f-trycua wants to merge 1 commit into
mainfrom
fix/serve-daemon-runs-cursor-overlay
Closed

fix(cua-driver-rs)(macos): run the agent-cursor overlay in the serve daemon#1788
f-trycua wants to merge 1 commit into
mainfrom
fix/serve-daemon-runs-cursor-overlay

Conversation

@f-trycua

@f-trycua f-trycua commented May 31, 2026

Copy link
Copy Markdown
Collaborator

The bug (why the agent cursor was never visible)

The overlay NSWindow + AppKit render loop were wired into the in-process mcp arm but never the serve daemon arm. In the daemon-proxy setup everyone actually runs — cua-driver mcp relaunches open -n -g … serve and proxies to it for correct TCC attribution — the daemon does the clicking but parked its main thread in serve_handle.join() and never inited/ran the overlay. So CMD_TX was unset and every OverlayCommand was a silent no-op; the cursor could not render.

Confirmed empirically on the running daemon: main thread in pthread_join, zero tiny_skia/SkyLight/NSWindow activity.

Fix

The Serve arm now builds cursor_cfg, inits the overlay channel before spawning the serve thread, and (when the cursor is enabled) parks main in overlay::run_on_main_thread() — mirroring the Mcp arm — instead of join. It self-guards on has_graphic_access() and falls back to join when there's no Window Server session, so headless serving is unaffected. PiP path unchanged.

Verified

After the fix the serve daemon's main thread runs __CFRunLoopRun / -[NSApplication run] with tiny_skia + SkyLight overlay activity, and still serves (list_windows over the socket works). On-screen pixel confirmation is a manual VM eyeball (redo the Calculator AX run → the agent cursor now renders + glides).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Optimized macOS startup: cursor overlay channel now initializes earlier in the process to ensure consistent availability across all configurations. Enhanced main-thread handling to improve execution flow management based on active features, whether using Picture-in-Picture mode, cursor overlay functionality, or running in standalone mode.

…daemon

The overlay NSWindow + AppKit render loop were only wired into the in-process
`mcp` arm. In the daemon-proxy setup users actually run (`mcp` relaunches
`open -n -g … serve` and proxies to it for correct TCC attribution), the
DAEMON performs the clicks/AX presses but never inited or ran the overlay —
its main thread parked in `serve_handle.join()`. So `set_agent_cursor_enabled`
flipped registry flags and clicks sent OverlayCommands, but CMD_TX was never
set → every cursor command was a silent no-op and the agent cursor never
appeared (confirmed: daemon main thread in pthread_join, zero tiny_skia /
SkyLight / NSWindow activity).

Fix: the Serve arm now builds cursor_cfg, inits the overlay channel before
spawning the serve thread, and — when the cursor is enabled — parks the main
thread in `overlay::run_on_main_thread()` (mirrors the Mcp arm) instead of
join. It self-guards on has_graphic_access() and falls back to join when the
daemon has no Window Server session, so headless serving is unaffected.

Verified: with the fix the serve daemon's main thread is now in
__CFRunLoopRun / -[NSApplication run] with tiny_skia + SkyLight overlay render
activity, and it still serves (list_windows over the socket works).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored Preview May 31, 2026 10:42pm

Request Review

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

On macOS serve startup, the code initializes the agent-cursor overlay channel immediately after parsing PiP config (when enabled) to align with the mcp arm's approach. Main-thread behavior then branches: if PiP is enabled it runs the AppKit main loop; otherwise, if cursor overlay is enabled it runs the overlay on the main thread then joins the serve thread; else it joins the serve thread.

Changes

macOS Serve Startup Sequence

Layer / File(s) Summary
Cursor overlay channel initialization on serve path
libs/cua-driver/rust/crates/cua-driver/src/main.rs
Agent-cursor overlay channel is initialized on the macOS serve path when enabled, positioned immediately after PiP config parsing. Comments document that the channel must be ready before main-thread execution to prevent cursor commands from becoming no-ops in the daemon-proxy relaunch setup.
Main-thread control flow branching
libs/cua-driver/rust/crates/cua-driver/src/main.rs
Main-thread behavior after permissions gate and serve-thread spawn branches on feature availability: PiP runs run_appkit_main_loop(); cursor overlay (when enabled) runs platform_macos::cursor::overlay::run_on_main_thread() then joins serve thread; otherwise joins serve thread directly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • trycua/cua#1773: Moves socket and serve-thread binding before permissions::run_if_needed in the same macOS serve startup sequence.
  • trycua/cua#1781: Adjusts cursor overlay AppKit startup and main-thread parking based on Window Server access probing.

Poem

A cursor that dances, aligned and aware,
On startup it blooms in the crisp morning air,
Now ready and waiting before threads entwine,
AppKit or overlay—the choice to design,
The main loop now knows what to do and when! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly describes the main change: enabling the agent-cursor overlay to run in the serve daemon on macOS. This matches the primary objective of fixing the overlay initialization issue in the daemon flow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/serve-daemon-runs-cursor-overlay
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/serve-daemon-runs-cursor-overlay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver/rust/crates/cua-driver/src/main.rs`:
- Around line 336-346: The PiP branch currently returns early and skips
initializing/rendering the cursor overlay when both pip_cfg.enabled and
cursor_cfg.enabled are true; update the control flow so that when
cursor_cfg.enabled is true you always call
platform_macos::cursor::overlay::run_on_main_thread() (even if pip_cfg.enabled
is also true), then run platform_macos::pip::run_appkit_main_loop() as needed
and finally join the serve_handle (serve_handle.join()) so the overlay actually
renders in combined PiP+overlay mode; check the existing maybe_init_pip() usage
to preserve any initialization ordering.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2e121c0-5a8f-40f2-ab87-488a0c1fe7ee

📥 Commits

Reviewing files that changed from the base of the PR and between 18e2cbb and ec94c94.

📒 Files selected for processing (1)
  • libs/cua-driver/rust/crates/cua-driver/src/main.rs

Comment on lines 336 to +346
if pip_cfg.enabled {
platform_macos::pip::run_appkit_main_loop();
} else if cursor_cfg.enabled {
// Render the agent-cursor overlay: park the main thread in the
// AppKit run loop so the overlay NSWindow draws. `run_on_main_thread`
// self-guards on `has_graphic_access()` and returns immediately
// when the daemon has no Window Server session — fall through to
// join so the daemon still serves headless. The serve thread runs
// on its background thread regardless.
platform_macos::cursor::overlay::run_on_main_thread();
let _ = serve_handle.join();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let the PiP branch bypass the cursor overlay.

When PiP and the cursor overlay are both enabled, this ordering never calls platform_macos::cursor::overlay::run_on_main_thread(). In the same file, the mcp path still does maybe_init_pip() and then runs the overlay on the main thread, so serve now regresses the combined mode to "overlay initialized but never rendered."

Suggested shape
-            if pip_cfg.enabled {
-                platform_macos::pip::run_appkit_main_loop();
-            } else if cursor_cfg.enabled {
+            if cursor_cfg.enabled {
                 // Render the agent-cursor overlay: park the main thread in the
                 // AppKit run loop so the overlay NSWindow draws. `run_on_main_thread`
                 // self-guards on `has_graphic_access()` and returns immediately
                 // when the daemon has no Window Server session — fall through to
                 // join so the daemon still serves headless. The serve thread runs
                 // on its background thread regardless.
                 platform_macos::cursor::overlay::run_on_main_thread();
                 let _ = serve_handle.join();
+            } else if pip_cfg.enabled {
+                platform_macos::pip::run_appkit_main_loop();
             } else {
                 let _ = serve_handle.join();
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if pip_cfg.enabled {
platform_macos::pip::run_appkit_main_loop();
} else if cursor_cfg.enabled {
// Render the agent-cursor overlay: park the main thread in the
// AppKit run loop so the overlay NSWindow draws. `run_on_main_thread`
// self-guards on `has_graphic_access()` and returns immediately
// when the daemon has no Window Server session — fall through to
// join so the daemon still serves headless. The serve thread runs
// on its background thread regardless.
platform_macos::cursor::overlay::run_on_main_thread();
let _ = serve_handle.join();
if cursor_cfg.enabled {
// Render the agent-cursor overlay: park the main thread in the
// AppKit run loop so the overlay NSWindow draws. `run_on_main_thread`
// self-guards on `has_graphic_access()` and returns immediately
// when the daemon has no Window Server session — fall through to
// join so the daemon still serves headless. The serve thread runs
// on its background thread regardless.
platform_macos::cursor::overlay::run_on_main_thread();
let _ = serve_handle.join();
} else if pip_cfg.enabled {
platform_macos::pip::run_appkit_main_loop();
} else {
let _ = serve_handle.join();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver/rust/crates/cua-driver/src/main.rs` around lines 336 - 346,
The PiP branch currently returns early and skips initializing/rendering the
cursor overlay when both pip_cfg.enabled and cursor_cfg.enabled are true; update
the control flow so that when cursor_cfg.enabled is true you always call
platform_macos::cursor::overlay::run_on_main_thread() (even if pip_cfg.enabled
is also true), then run platform_macos::pip::run_appkit_main_loop() as needed
and finally join the serve_handle (serve_handle.join()) so the overlay actually
renders in combined PiP+overlay mode; check the existing maybe_init_pip() usage
to preserve any initialization ordering.

@f-trycua

Copy link
Copy Markdown
Collaborator Author

Superseded by #1790 (rebased onto main after #1787).

@f-trycua f-trycua closed this May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant