macOS App
OpenClaw macOS Companion (menu bar + gateway broker)
Section titled “OpenClaw macOS Companion (menu bar + gateway broker)”The macOS app is the menu‑bar companion for OpenClaw. It owns permissions, manages/attaches to the Gateway locally (launchd or manual), and exposes macOS capabilities to the agent as a node.
What it does
Section titled “What it does”- Shows native notifications and status in the menu bar.
- Owns TCC prompts (Notifications, Accessibility, Screen Recording, Microphone, Speech Recognition, Automation/AppleScript).
- Runs or connects to the Gateway (local or remote).
- Exposes macOS‑only tools (Canvas, Camera, Screen Recording,
system.run). - Starts the local node host service in remote mode (launchd), and stops it in local mode.
- Optionally hosts PeekabooBridge for UI automation.
- Installs the global CLI (
openclaw) via npm/pnpm on request (bun not recommended for the Gateway runtime).
Local vs remote mode
Section titled “Local vs remote mode”- Local (default): the app attaches to a running local Gateway if present;
otherwise it enables the launchd service via
openclaw gateway install. - Remote: the app connects to a Gateway over SSH/Tailscale and never starts a local process. The app starts the local node host service so the remote Gateway can reach this Mac. The app does not spawn the Gateway as a child process. Gateway discovery now prefers Tailscale MagicDNS names over raw tailnet IPs, so the Mac app recovers more reliably when tailnet IPs change.
Launchd control
Section titled “Launchd control”The app manages a per‑user LaunchAgent labeled ai.openclaw.gateway
(or ai.openclaw.<profile> when using --profile/OPENCLAW_PROFILE; legacy com.openclaw.* still unloads).
launchctl kickstart -k gui/$UID/ai.openclaw.gatewaylaunchctl bootout gui/$UID/ai.openclaw.gatewayReplace the label with ai.openclaw.<profile> when running a named profile.
If the LaunchAgent isn’t installed, enable it from the app or run
openclaw gateway install.
Node capabilities (mac)
Section titled “Node capabilities (mac)”The macOS app presents itself as a node. Common commands:
- Canvas:
canvas.present,canvas.navigate,canvas.eval,canvas.snapshot,canvas.a2ui.* - Camera:
camera.snap,camera.clip - Screen:
screen.record - System:
system.run,system.notify
The node reports a permissions map so agents can decide what’s allowed.
Node service + app IPC:
- When the headless node host service is running (remote mode), it connects to the Gateway WS as a node.
system.runexecutes in the macOS app (UI/TCC context) over a local Unix socket; prompts + output stay in-app.
Diagram (SCI):
Gateway -> Node Service (WS) | IPC (UDS + token + HMAC + TTL) v Mac App (UI + TCC + system.run)Exec approvals (system.run)
Section titled “Exec approvals (system.run)”system.run is controlled by Exec approvals in the macOS app (Settings → Exec approvals).
Security + ask + allowlist are stored locally on the Mac in:
~/.openclaw/exec-approvals.jsonExample:
{ "version": 1, "defaults": { "security": "deny", "ask": "on-miss" }, "agents": { "main": { "security": "allowlist", "ask": "on-miss", "allowlist": [{ "pattern": "/opt/homebrew/bin/rg" }] } }}Notes:
allowlistentries are glob patterns for resolved binary paths.- Raw shell command text that contains shell control or expansion syntax (
&&,||,;,|,`,$,<,>,(,)) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary). - Choosing “Always Allow” in the prompt adds that command to the allowlist.
system.runenvironment overrides are filtered (dropsPATH,DYLD_*,LD_*,NODE_OPTIONS,PYTHON*,PERL*,RUBYOPT,SHELLOPTS,PS4) and then merged with the app’s environment.- For shell wrappers (
bash|sh|zsh ... -c/-lc), request-scoped environment overrides are reduced to a small explicit allowlist (TERM,LANG,LC_*,COLORTERM,NO_COLOR,FORCE_COLOR). - For allow-always decisions in allowlist mode, known dispatch wrappers (
env,nice,nohup,stdbuf,timeout) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.
Deep links
Section titled “Deep links”The app registers the openclaw:// URL scheme for local actions.
openclaw://agent
Section titled “openclaw://agent”Triggers a Gateway agent request.
open 'openclaw://agent?message=Hello%20from%20deep%20link'Query parameters:
message(required)sessionKey(optional)thinking(optional)deliver/to/channel(optional)timeoutSeconds(optional)key(optional unattended mode key)
Safety:
- Without
key, the app prompts for confirmation. - Without
key, the app enforces a short message limit for the confirmation prompt and ignoresdeliver/to/channel. - With a valid
key, the run is unattended (intended for personal automations).
Onboarding flow (typical)
Section titled “Onboarding flow (typical)”- Install and launch OpenClaw.app.
- Complete the permissions checklist (TCC prompts).
- Ensure Local mode is active and the Gateway is running.
- Install the CLI if you want terminal access.
State dir placement (macOS)
Section titled “State dir placement (macOS)”Avoid putting your OpenClaw state dir in iCloud or other cloud-synced folders. Sync-backed paths can add latency and occasionally cause file-lock/sync races for sessions and credentials.
Prefer a local non-synced state path such as:
OPENCLAW_STATE_DIR=~/.openclawIf openclaw doctor detects state under:
~/Library/Mobile Documents/com~apple~CloudDocs/...~/Library/CloudStorage/...
it will warn and recommend moving back to a local path.
Build & dev workflow (native)
Section titled “Build & dev workflow (native)”cd apps/macos && swift buildswift run OpenClaw(or Xcode)- Package app:
scripts/package-mac-app.sh
Debug gateway connectivity (macOS CLI)
Section titled “Debug gateway connectivity (macOS CLI)”Use the debug CLI to exercise the same Gateway WebSocket handshake and discovery logic that the macOS app uses, without launching the app.
cd apps/macosswift run openclaw-mac connect --jsonswift run openclaw-mac discover --timeout 3000 --jsonConnect options:
--url <ws://host:port>: override config--mode <local|remote>: resolve from config (default: config or local)--probe: force a fresh health probe--timeout <ms>: request timeout (default:15000)--json: structured output for diffing
Discovery options:
--include-local: include gateways that would be filtered as “local”--timeout <ms>: overall discovery window (default:2000)--json: structured output for diffing
Tip: compare against openclaw gateway discover --json to see whether the
macOS app’s discovery pipeline (NWBrowser + tailnet DNS‑SD fallback) differs from
the Node CLI’s dns-sd based discovery.
Remote connection plumbing (SSH tunnels)
Section titled “Remote connection plumbing (SSH tunnels)”When the macOS app runs in Remote mode, it opens an SSH tunnel so local UI components can talk to a remote Gateway as if it were on localhost.
Control tunnel (Gateway WebSocket port)
Section titled “Control tunnel (Gateway WebSocket port)”- Purpose: health checks, status, Web Chat, config, and other control-plane calls.
- Local port: the Gateway port (default
18789), always stable. - Remote port: the same Gateway port on the remote host.
- Behavior: no random local port; the app reuses an existing healthy tunnel or restarts it if needed.
- SSH shape:
ssh -N -L <local>:127.0.0.1:<remote>with BatchMode + ExitOnForwardFailure + keepalive options. - IP reporting: the SSH tunnel uses loopback, so the gateway will see the node
IP as
127.0.0.1. Use Direct (ws/wss) transport if you want the real client IP to appear (see macOS remote access).
For setup steps, see macOS remote access. For protocol details, see Gateway protocol.