Skip to content

fix(cua-driver-rs)(macos): bind serve socket before the permissions gate (#1761) - #1773

Merged
f-trycua merged 1 commit into
mainfrom
fix/serve-bind-socket-before-permissions-gate
May 30, 2026
Merged

fix(cua-driver-rs)(macos): bind serve socket before the permissions gate (#1761)#1773
f-trycua merged 1 commit into
mainfrom
fix/serve-bind-socket-before-permissions-gate

Conversation

@f-trycua

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

Copy link
Copy Markdown
Collaborator

Problem

On macOS first launch with com.trycua.driver ungranted, the Serve arm ran the blocking permissions gate (run_if_needed) before run_serve_cmd bound the Unix socket. The gate sits in wait_for_grants (prompting + re-exec-looping) until the user grants or the 10-min deadline elapses, so the socket never appears. A daemon launched via open -n -g -a CuaDriver --args serve (the only launch that gives correct TCC attribution + System Settings registration) is therefore unreachable for minutes — permissions grant and MCP clients can't even get a "pending" answer.

Fix

Reorder the macOS serve arm (main.rs): serve now runs on a background thread first (it binds the socket — a Unix socket + tokio accept loop has no main-thread requirement), and the gate runs on the main thread (kept there because its prompt APIs request_accessibility / request_screen_recording and the NSPanel must stay on main). The socket is binding/bound within ~1s while the gate works toward the grant. Main stays alive via run_appkit_main_loop() (PiP) or serve_handle.join() (non-PiP).

Why the re-exec stays + cadence tradeoff (gate.rs): AXIsProcessTrusted() is cached per process, so a process that cached false cannot see a later Accessibility grant without re-executing (execvp, fresh process). The re-exec is load-bearing and is NOT removed. But now that serve runs concurrently, each reexec_self() restarts the whole daemon and flaps the socket. Raised EXEC_AFTER_POLLS 5 → 25 (~25s between re-execs at the 1s poll interval) to trade grant-detection latency (fine on a human-clicking-through-Settings timescale) for socket stability. Added a "restarting daemon" log before each re-exec so the restart isn't silent.

Stale-socket unlink: already present — the macOS #[cfg(unix)] run_serve does std::fs::remove_file(socket_path) before UnixListener::bind, and is_daemon_listening is a connect probe that returns false after the old process is gone. No change needed; verified the restart rebinds cleanly without EADDRINUSE.

permissions grant polling (cli.rs): the socket now appears before the grant, so a single check_permissions query returns "pending". Changed to poll check_permissions via the daemon every 2s up to 180s, tolerating transient connection failures during a re-exec restart, until both grants flip true (success) or timeout (tells the user to approve the CuaDriver dialog + re-run). The "daemon already running" fast branch is preserved.

Happy path unchanged: when current_status().all_granted() is true, run_if_needed fast-returns at gate.rs:213 — no banner, no polling, no flapping. Serve still starts normally.

Scope

macOS-only. The non-macOS Serve arm already spawns serve on a thread and has no gate — untouched.

Test plan (live, maintainer — needs GUI + grant clicks)

  • (a) grants present → instant: launch serve with Accessibility + Screen Recording already granted to com.trycua.driver. Socket binds immediately, gate fast-returns, no flap, no regression vs current behavior.
  • (b) ungranted first launch: tccutil reset Accessibility com.trycua.driver && tccutil reset ScreenCapture com.trycua.driver, then cua-driver permissions grant. Verify: socket appears fast (within ~1s, permissions status answers "pending" immediately); the system prompt reads "Cua Driver"; after granting both, the daemon stabilizes and grant reports granted (no infinite flap; the ~25s re-exec cadence means at most a brief socket blip per recheck).
  • (c) MCP still works: cua-driver mcp via Claude Code connects and drives tools normally.
  • (d) never-granted → bounded: launch ungranted and never grant. Confirm no infinite flap — the gate stops at its deadline (default 10 min), logs the timeout, and the daemon keeps serving (tool calls touching AX/Screen Recording fail with the underlying TCC error).

Build

  • cargo build --release -p cua-driver
  • cargo test --no-run -p platform-macos ✅ (compile clean; the pre-existing libswift_Concurrency rpath run failure is unrelated — compile is what matters here)

DRAFT — this is load-bearing daemon-startup code that needs live TCC-state verification before merge. Please do not merge until the test plan above passes on a real machine.

Refs #1761

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements
    • Enhanced macOS permissions verification with polling to better handle transient permission state changes
    • Improved timeout error messages with clearer System Settings navigation guidance
    • Optimized daemon initialization sequence for more reliable startup flow

Review Change Stack

…ate (#1761)

On first launch with `com.trycua.driver` ungranted, the macOS `Serve` arm
ran the blocking permissions gate BEFORE `run_serve_cmd` bound the Unix
socket. While the gate sat in `wait_for_grants` (prompting + re-exec
looping), the socket never appeared, so a daemon launched via
`open -n -g -a CuaDriver --args serve` was unreachable for minutes —
`permissions grant` and MCP clients couldn't even get a "pending" answer.

Reorder the macOS serve arm: run serve on a background thread first (it
binds the socket — a Unix socket + tokio accept loop has no main-thread
requirement) and run the gate on the main thread (its prompt APIs and the
NSPanel must stay on main). The daemon is reachable within ~1s while the
gate works toward the grant. On grant the gate's `reexec_self()` restarts
the whole daemon cleanly; `run_serve` already unlinks the stale socket
file before re-binding, so the rebind is fast.

Because serve now runs concurrently, each re-exec restarts the daemon and
flaps the socket. Raise `EXEC_AFTER_POLLS` 5 -> 25 (~25s between
re-execs) to trade grant-detection latency for socket stability, and log
"restarting daemon" before each re-exec. The re-exec stays — it's the
only way to pick up an Accessibility grant (`AXIsProcessTrusted` is cached
per process).

`permissions grant` now polls `check_permissions` via the daemon every 2s
up to 180s (tolerating transient failures during a re-exec restart) until
both grants flip true, instead of a single query that returns "pending"
now that the socket appears before the grant.

Refs #1761

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

vercel Bot commented May 30, 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 May 30, 2026 8:48pm

Request Review

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f29248e-9a01-4630-838f-6836720fec30

📥 Commits

Reviewing files that changed from the base of the PR and between 6036ab4 and 870b9f5.

📒 Files selected for processing (3)
  • libs/cua-driver/rust/crates/cua-driver/src/cli.rs
  • libs/cua-driver/rust/crates/cua-driver/src/main.rs
  • libs/cua-driver/rust/crates/platform-macos/src/permissions/gate.rs

📝 Walkthrough

Walkthrough

Daemon startup is reordered to bind the Unix socket before running the macOS permissions gate. The CLI transitions from a single permissions check to polling the daemon for up to 180 seconds, with updated timeout messaging. The daemon's re-exec threshold increases from 5 to 25 polls to reduce restart frequency during permission transitions.

Changes

macOS Permissions Gate and Startup Flow

Layer / File(s) Summary
Daemon startup reordering and threading
libs/cua-driver/rust/crates/cua-driver/src/main.rs
The serve thread is spawned on a background OS thread before the permissions gate runs on the main thread. After the gate, main thread behavior diverges by PiP setting: it enters the AppKit loop when PiP is enabled, or joins the serve thread when disabled, replacing the previous direct execution on the main thread.
CLI permissions polling and timeout
libs/cua-driver/rust/crates/cua-driver/src/cli.rs
run_permissions_grant now polls the daemon's check_permissions endpoint repeatedly until both accessibility and screen_recording permissions are granted or a 180-second deadline is reached, tolerating transient daemon failures. Timeout error output is updated to "Timed out waiting on: {missing}" with revised System Settings navigation instructions.
Gate re-exec polling threshold
libs/cua-driver/rust/crates/platform-macos/src/permissions/gate.rs
The EXEC_AFTER_POLLS constant is increased from 5 to 25, extending the re-exec cadence from ~5 seconds to ~25 seconds to reduce unnecessary daemon restarts while permission state transitions occur.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • trycua/cua#1565: Both PRs modify the macOS permissions gate flow; this PR adjusts polling/re-exec timing and startup ordering for check_permissions, while the retrieved PR changes permissions/gate.rs's run_if_needed to optionally show an NSPanel and conditionally open System Settings.
  • trycua/cua#1529: This PR builds on the PermissionsGate implementation introduced in the retrieved PR, updating the polling/re-exec threshold and serve startup gating behavior.
  • trycua/cua#1567: Both PRs modify wait_for_grants polling logic in permissions/gate.rs; this PR increases the re-exec threshold, while the retrieved PR adds reexec_self() to refresh TCC state.

Poem

🐰 A daemon stands in line to serve,
With sockets bound and threads that curve,
Permissions polled with patient care,
Till System Settings grants its prayer—
No restless re-execs to spare! 📡

✨ 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-bind-socket-before-permissions-gate

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.

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