Files
openclaw/apps/macos

OpenClaw macOS app (dev + signing)

Quick dev run

# from repo root
scripts/restart-mac.sh

Options:

scripts/restart-mac.sh --no-sign   # fastest dev; ad-hoc signing (TCC permissions do not stick)
scripts/restart-mac.sh --sign      # force code signing (requires cert)
scripts/restart-mac.sh --background-only # keep services running without automatic windows

--background-only suppresses first-run onboarding, update and CLI prompts, and the --chat/--dashboard auto-open helpers. Pairing, control-channel, and Mac node services still start. It also keeps GUI-owned onboarding and saved Gateway profile Keychain state cold, so a signer or ACL transition cannot raise a SecurityAgent prompt during unattended work. The primary Gateway route still comes from the normal environment/config endpoint. Combine it with --attach-only when an external process owns the local Gateway.

App profiles

Launch a fully isolated app instance with the same profile name used by the CLI:

OPENCLAW_PROFILE=work /Applications/OpenClaw.app/Contents/MacOS/OpenClaw

Profile names use 164 lowercase letters, numbers, underscores, or hyphens and must start with a letter or number. default selects the normal app; gateway, mac, and node are reserved LaunchAgent identities.

scripts/restart-mac.sh intentionally rejects named profiles because its packaging cleanup is host-global. Build/package normally, then launch the named profile directly with the command above.

A named profile keeps state in ~/.openclaw-<name>, uses its own app defaults, Keychain services, duplicate-instance lock, and the CLI-managed Gateway service ai.openclaw.<name>. Unless config or environment selects a port, each profile derives a stable port in the profile 20000...59999 range. The app does not install or modify the host-global Mac node service or OpenClaw login item while a profile is active. The runtime child node still runs in process as usual. App relocation, Sparkle updates, and post-update service repair are disabled in profile mode; update the installed app through the normal default-profile workflow.

Packaging flows

Development bundle (signed but not notarized):

scripts/package-mac-app.sh

This creates dist/OpenClaw.app and signs it via scripts/codesign-mac-app.sh. It is not a distribution artifact. For a notarized app ZIP and DMG, use:

scripts/package-mac-dist.sh

For an unattended Peekaboo elevation host, use the closed Foundation signing profile and source-addressed ZIP workflow. package is an internal release operator command: it requires the OpenClaw Foundation signing identity and notarization credentials, and its archive is not a general-download artifact.

scripts/mac-elevation-host.sh package
cd dist/elevation-host
shasum -a 256 -c "OpenClaw-<full-source-sha>-stable.zip.sha256"
shasum -a 256 -c "OpenClaw-<full-source-sha>-stable-installer.sh.sha256"
./OpenClaw-<full-source-sha>-stable-installer.sh install \
  --archive "OpenClaw-<full-source-sha>-stable.zip"
./OpenClaw-<full-source-sha>-stable-installer.sh status

The elevation package is ZIP-only, notarized and stapled, contains exactly OpenClaw.app, omits Apple Events entitlements, records an immutable receipt, and verifies a freshly extracted copy. The same source-addressed artifact set includes an executable installer copied from that exact Git commit plus separate archive and installer checksum files, so a target Mac does not need a source checkout. Transfer the complete set and verify both checksums before running the installer. Installation owns the separate ai.openclaw.mac.elevation-host launchd job with RunAtLoad and KeepAlive. It refuses to replace or race the ordinary ai.openclaw.mac Launch at login job. recover restores the recorded prior bundle after a failed cutover; uninstall removes only the elevation job and preserves the app, state, Keychain, TCC, and recovery receipt. Installation exits successfully once the launchd-owned process is Bridge-ready; missing TCC remains a degraded status result until the required grants are present.

Signing behavior

Auto-selects identity (first match):

  1. Developer ID Application
  2. Apple Distribution
  3. Apple Development
  4. first available identity

If none found:

  • errors by default
  • set ALLOW_ADHOC_SIGNING=1 or SIGN_IDENTITY="-" to ad-hoc sign

Team ID audit (Sparkle mismatch guard)

After signing, we read the app bundle Team ID and compare every Mach-O inside the app. If any embedded binary has a different Team ID, signing fails.

Skip the audit:

SKIP_TEAM_ID_CHECK=1 scripts/package-mac-app.sh

Library validation workaround (dev only)

If Sparkle Team ID mismatch blocks loading (common with Apple Development certs), opt in:

DISABLE_LIBRARY_VALIDATION=1 scripts/package-mac-app.sh

This adds com.apple.security.cs.disable-library-validation to app entitlements. Use for local dev only; keep off for release builds.

Useful env flags

  • SIGN_IDENTITY="Apple Development: Your Name (TEAMID)"
  • ALLOW_ADHOC_SIGNING=1 (ad-hoc, TCC permissions do not persist)
  • CODESIGN_TIMESTAMP=off (offline debug)
  • DISABLE_LIBRARY_VALIDATION=1 (dev-only Sparkle workaround)
  • SKIP_TEAM_ID_CHECK=1 (bypass audit)