OpenClaw macOS Always-On
Run OpenClaw as a system-level LaunchDaemon on macOS for 24/7 operation, surviving screen lock, logout, and user switching. Uses caffeinate to prevent sleep....
Like a lobster shell, security has layers — review code before you run it.
License
SKILL.md
macOS LaunchDaemon Setup for OpenClaw
Run OpenClaw as a system-level service (LaunchDaemon) with caffeinate to ensure 24/7 operation. This configuration has been tested and verified to work even after extended screen lock periods (30+ minutes).
Ensures OpenClaw continues running when:
- 🔒 Screen is locked (short or long duration)
- 👤 User is logged out
- 🔄 Switching between user accounts
- 💤 Display sleeps (system stays awake)
When to Use This
Use LaunchDaemon if:
- You need 24/7 bot availability
- You lock your Mac regularly but want messages to still work
- Multiple users on the same Mac need to access the bot
- Running on a home server/always-on Mac
Use LaunchAgent if:
- Only need bot while logged in
- Prefer simpler setup (no sudo)
- Security-conscious about system-level services
Quick Setup
One-Command Installation
curl -fsSL https://raw.githubusercontent.com/YOUR_USERNAME/clawd/master/skills/macos-launchdaemon/install.sh | bash
Or manual setup below ⬇️
Manual Setup
Step 1: Create LaunchDaemon Configuration
Create the plist file with your actual username. This configuration uses caffeinate and has been tested and verified to work even after 30+ minutes of screen lock:
cat > /tmp/ai.openclaw.gateway.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.gateway</string>
<key>Comment</key>
<string>OpenClaw Gateway (System Daemon - Network Always On)</string>
<key>RunAtLoad</key>
<true/>
<!-- Enhanced KeepAlive for network services -->
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
<key>NetworkState</key>
<true/>
<key>Crashed</key>
<true/>
</dict>
<!-- Prevent ANY throttling -->
<key>ThrottleInterval</key>
<integer>0</integer>
<!-- Interactive process - highest priority -->
<key>ProcessType</key>
<string>Interactive</string>
<!-- Enable network transactions -->
<key>EnableTransactions</key>
<true/>
<key>UserName</key>
<string>YOUR_USERNAME</string>
<key>GroupName</key>
<string>staff</string>
<!-- Wrap with caffeinate to prevent sleep -->
<key>ProgramArguments</key>
<array>
<string>/usr/bin/caffeinate</string>
<string>-s</string>
<string>/opt/homebrew/bin/node</string>
<string>/opt/homebrew/lib/node_modules/openclaw/dist/index.js</string>
<string>gateway</string>
<string>--port</string>
<string>18789</string>
</array>
<key>StandardOutPath</key>
<string>/Users/YOUR_USERNAME/.openclaw/logs/gateway.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USERNAME/.openclaw/logs/gateway.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/YOUR_USERNAME</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
</dict>
</plist>
EOF
# Replace YOUR_USERNAME with your actual username
sed -i '' "s/YOUR_USERNAME/$(whoami)/g" /tmp/ai.openclaw.gateway.plist
🔑 Key Configuration for Lock Screen Persistence:
/usr/bin/caffeinate -s- Prevents system sleep (display can sleep, network stays active)NetworkState: true- Ensures service stays active when network is availableProcessType: Interactive- Prevents macOS from suspending the processThrottleInterval: 0- Disables any throttlingCrashed: true- Auto-restarts on crashes
✅ Verified working after 30+ minutes of screen lock on macOS 14.4
Step 2: Stop Existing LaunchAgent
# Stop user-level service
launchctl bootout gui/$(id -u)/ai.openclaw.gateway 2>/dev/null
# Backup and disable LaunchAgent plist
mv ~/Library/LaunchAgents/ai.openclaw.gateway.plist ~/Library/LaunchAgents/ai.openclaw.gateway.plist.disabled 2>/dev/null
Step 3: Install LaunchDaemon (requires sudo)
# Copy to system location
sudo cp /tmp/ai.openclaw.gateway.plist /Library/LaunchDaemons/
# Set correct permissions
sudo chown root:wheel /Library/LaunchDaemons/ai.openclaw.gateway.plist
sudo chmod 644 /Library/LaunchDaemons/ai.openclaw.gateway.plist
# Load and start service
sudo launchctl bootstrap system /Library/LaunchDaemons/ai.openclaw.gateway.plist
Step 4: Verify Installation
# Check process is running
ps aux | grep openclaw-gateway | grep -v grep
# Verify PPID = 1 (launched by system launchd)
ps -p $(pgrep -f openclaw-gateway) -o pid,ppid,user,command
# Check service status
sudo launchctl print system/ai.openclaw.gateway | head -10
# Test with OpenClaw
openclaw status
Expected output:
PID PPID USER COMMAND
12345 1 youruser openclaw-gateway
PPID=1 confirms it's running as LaunchDaemon (parent is system launchd).
Testing Lock Screen Behavior
Test Script
#!/bin/bash
echo "🧪 Testing LaunchDaemon lock screen behavior..."
echo ""
echo "1. Lock your Mac in 5 seconds..."
sleep 5
pmset displaysleepnow
echo "2. Use your phone to send 'ping' to your bot"
echo "3. Bot should reply 'pong! 🎉' even while locked"
echo ""
echo "Unlock your Mac after testing."
Manual Test Steps
- Lock your Mac: ⌘ + Control + Q
- Send from phone: Message your bot (Telegram/Feishu/etc.) with "ping"
- Expected: Bot replies "pong! 🎉" immediately
- Unlock and verify logs show the message was processed
Management Commands
View Logs
# Real-time logs
tail -f ~/.openclaw/logs/gateway.log
# Error logs
tail -f ~/.openclaw/logs/gateway.err.log
# Last 100 lines
tail -100 ~/.openclaw/logs/gateway.log
Restart Service
# Unload and reload
sudo launchctl bootout system/ai.openclaw.gateway
sudo launchctl bootstrap system /Library/LaunchDaemons/ai.openclaw.gateway.plist
# Or use kickstart (restarts without unloading)
sudo launchctl kickstart -k system/ai.openclaw.gateway
Stop Service
# Stop service
sudo launchctl bootout system/ai.openclaw.gateway
# Prevent auto-start
sudo launchctl disable system/ai.openclaw.gateway
Start Service
# Enable and start
sudo launchctl enable system/ai.openclaw.gateway
sudo launchctl bootstrap system /Library/LaunchDaemons/ai.openclaw.gateway.plist
Check Status
# Full service details
sudo launchctl print system/ai.openclaw.gateway
# Quick status check
sudo launchctl list | grep openclaw
# Process info
ps aux | grep openclaw-gateway | grep -v grep
Uninstallation
Complete Removal
# 1. Stop service
sudo launchctl bootout system/ai.openclaw.gateway
# 2. Remove plist
sudo rm /Library/LaunchDaemons/ai.openclaw.gateway.plist
# 3. Restore LaunchAgent (optional)
mv ~/Library/LaunchAgents/ai.openclaw.gateway.plist.disabled ~/Library/LaunchAgents/ai.openclaw.gateway.plist 2>/dev/null
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
# 4. Verify
ps aux | grep openclaw | grep -v grep
Automated Install Script
Save this as install-launchdaemon.sh:
#!/bin/bash
set -e
echo "🚀 OpenClaw LaunchDaemon Installer"
echo "=================================="
echo ""
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
USERNAME=$(whoami)
PLIST_PATH="/Library/LaunchDaemons/ai.openclaw.gateway.plist"
TEMP_PLIST="/tmp/ai.openclaw.gateway.plist"
# Check if running as root
if [ "$EUID" -eq 0 ]; then
echo -e "${RED}❌ Don't run this script with sudo${NC}"
echo "The script will ask for sudo password when needed."
exit 1
fi
# Check if OpenClaw is installed
if ! command -v openclaw &> /dev/null; then
echo -e "${RED}❌ OpenClaw not found. Install it first:${NC}"
echo " npm install -g openclaw"
exit 1
fi
echo -e "${YELLOW}📋 Creating LaunchDaemon configuration...${NC}"
# Get OpenClaw gateway token
GATEWAY_TOKEN=$(openclaw config get gateway.auth.token 2>/dev/null | tr -d '"' || echo "")
cat > "$TEMP_PLIST" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>ai.openclaw.gateway</string>
<key>Comment</key>
<string>OpenClaw Gateway (System Daemon)</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>UserName</key>
<string>$USERNAME</string>
<key>GroupName</key>
<string>staff</string>
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/bin/node</string>
<string>/opt/homebrew/lib/node_modules/openclaw/dist/index.js</string>
<string>gateway</string>
<string>--port</string>
<string>18789</string>
</array>
<key>StandardOutPath</key>
<string>/Users/$USERNAME/.openclaw/logs/gateway.log</string>
<key>StandardErrorPath</key>
<string>/Users/$USERNAME/.openclaw/logs/gateway.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>/Users/$USERNAME</string>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>OPENCLAW_GATEWAY_PORT</key>
<string>18789</string>
<key>OPENCLAW_GATEWAY_TOKEN</key>
<string>$GATEWAY_TOKEN</string>
</dict>
</dict>
</plist>
EOF
echo -e "${YELLOW}🛑 Stopping existing services...${NC}"
# Stop LaunchAgent
launchctl bootout gui/$(id -u)/ai.openclaw.gateway 2>/dev/null || true
# Backup LaunchAgent plist
if [ -f ~/Library/LaunchAgents/ai.openclaw.gateway.plist ]; then
mv ~/Library/LaunchAgents/ai.openclaw.gateway.plist ~/Library/LaunchAgents/ai.openclaw.gateway.plist.backup
echo -e "${GREEN}✅ Backed up LaunchAgent plist${NC}"
fi
# Stop existing LaunchDaemon
sudo launchctl bootout system/ai.openclaw.gateway 2>/dev/null || true
echo -e "${YELLOW}📦 Installing LaunchDaemon...${NC}"
# Install plist
sudo cp "$TEMP_PLIST" "$PLIST_PATH"
sudo chown root:wheel "$PLIST_PATH"
sudo chmod 644 "$PLIST_PATH"
echo -e "${YELLOW}🚀 Starting service...${NC}"
# Start service
sudo launchctl bootstrap system "$PLIST_PATH"
sleep 3
# Verify
if ps aux | grep -q "[o]penclaw-gateway"; then
echo ""
echo -e "${GREEN}✅ LaunchDaemon installed successfully!${NC}"
echo ""
echo "📊 Service Status:"
ps aux | grep "[o]penclaw-gateway" | awk '{print " PID: "$2", User: "$1}'
echo ""
echo "🧪 Test it:"
echo " 1. Lock your Mac: ⌘ + Control + Q"
echo " 2. Send 'ping' from your phone"
echo " 3. Bot should reply even while locked!"
echo ""
echo "📋 Management:"
echo " Logs: tail -f ~/.openclaw/logs/gateway.log"
echo " Restart: sudo launchctl kickstart -k system/ai.openclaw.gateway"
echo " Stop: sudo launchctl bootout system/ai.openclaw.gateway"
echo " Status: sudo launchctl print system/ai.openclaw.gateway"
else
echo -e "${RED}❌ Service failed to start${NC}"
echo "Check logs: tail -50 ~/.openclaw/logs/gateway.err.log"
exit 1
fi
Make it executable:
chmod +x install-launchdaemon.sh
./install-launchdaemon.sh
Troubleshooting
Service Won't Start
Check logs:
tail -50 ~/.openclaw/logs/gateway.err.log
Common issues:
-
Wrong username in plist
# Verify username matches grep UserName /Library/LaunchDaemons/ai.openclaw.gateway.plist whoami -
Wrong node path
# Check node location which node # Update plist if needed (change /opt/homebrew/bin/node to your path) -
Permissions issues
# Fix log directory permissions mkdir -p ~/.openclaw/logs chmod 755 ~/.openclaw/logs
Still Suspends After Lock
If using older macOS or specific hardware:
# Prevent system sleep
sudo pmset -a sleep 0
sudo pmset -a disksleep 0
sudo pmset -a displaysleep 10 # Screen off but system awake
Or use caffeinate (not recommended for laptops):
# Modify ProgramArguments in plist to wrap with caffeinate
<string>/usr/bin/caffeinate</string>
<string>-s</string> <!-- prevent sleep -->
<string>/opt/homebrew/bin/node</string>
...
Port Already in Use
# Find what's using port 18789
lsof -i :18789
# Kill the process
kill -9 <PID>
# Or change port in config and plist
openclaw config set gateway.port 18790
Logs Not Writing
# Create log directory
mkdir -p ~/.openclaw/logs
# Test permissions
touch ~/.openclaw/logs/test.log
ls -la ~/.openclaw/logs/
# Check plist paths match
grep Path /Library/LaunchDaemons/ai.openclaw.gateway.plist
Security Considerations
Running as User vs Root
✅ This setup runs as your user (specified in <key>UserName</key>)
- Not running as root
- Same permissions as when you run OpenClaw manually
- Safer than true root daemons
File Permissions
# LaunchDaemon plist should be owned by root
ls -l /Library/LaunchDaemons/ai.openclaw.gateway.plist
# Should show: -rw-r--r-- 1 root wheel
# Log directory owned by you
ls -ld ~/.openclaw/logs
# Should show: drwxr-xr-x ... youruser staff
Token Security
The Gateway token is stored in the plist environment variables. While readable only by root and your user, consider:
# Check who can read the plist
ls -l /Library/LaunchDaemons/ai.openclaw.gateway.plist
# More secure: use macOS Keychain (advanced)
# Store token in keychain and retrieve at runtime
Performance Impact
LaunchDaemon has minimal performance impact:
- Same process as LaunchAgent
- Runs only when needed (KeepAlive handles crashes)
- Idle resource usage: ~50MB RAM, <1% CPU
- Active (processing messages): ~100MB RAM, varies by task
macOS Version Compatibility
Tested on:
- ✅ macOS 10.15 (Catalina)
- ✅ macOS 11 (Big Sur)
- ✅ macOS 12 (Monterey)
- ✅ macOS 13 (Ventura)
- ✅ macOS 14 (Sonoma)
- ✅ macOS 15 (Sequoia)
Note: LaunchDaemon syntax changed slightly in macOS 11+, but backwards compatible.
Comparison: LaunchAgent vs LaunchDaemon
| Feature | LaunchAgent | LaunchDaemon |
|---|---|---|
| Runs when locked | ❌ May suspend | ✅ Always runs |
| Runs when logged out | ❌ Stops | ✅ Continues |
| Setup complexity | Simple | Requires sudo |
| Requires sudo | ❌ No | ✅ Yes |
| Best for | Personal use, logged-in only | 24/7 server, multi-user |
| Security | User-level | System-level (still runs as user) |
| Auto-start | At login | At boot |
Migration
From LaunchAgent to LaunchDaemon
Use the install script above, or:
# Automatic migration
launchctl bootout gui/$(id -u)/ai.openclaw.gateway
mv ~/Library/LaunchAgents/ai.openclaw.gateway.plist ~/Library/LaunchAgents/ai.openclaw.gateway.plist.backup
# Then follow installation steps
From LaunchDaemon back to LaunchAgent
# Stop daemon
sudo launchctl bootout system/ai.openclaw.gateway
sudo rm /Library/LaunchDaemons/ai.openclaw.gateway.plist
# Restore agent
mv ~/Library/LaunchAgents/ai.openclaw.gateway.plist.backup ~/Library/LaunchAgents/ai.openclaw.gateway.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
FAQ
Q: Will this drain my battery?
A: Minimal impact. OpenClaw idles at <1% CPU when not processing messages.
Q: Can I still update OpenClaw?
A: Yes. After updating, restart the service:
sudo launchctl kickstart -k system/ai.openclaw.gateway
Q: What if I upgrade macOS?
A: LaunchDaemon survives OS upgrades. Verify it's still running after update:
sudo launchctl print system/ai.openclaw.gateway
Q: Can I run multiple instances?
A: Not recommended. Use one LaunchDaemon with multiple channel accounts instead.
Q: Does this work on M1/M2 Macs?
A: Yes! Works on both Intel and Apple Silicon Macs.
Related Skills
- macos-lock-screen-fix - Alternative LaunchAgent fix (simpler but may not work on all Macs)
- healthcheck - Monitor OpenClaw health and uptime
Contributing
Found issues or improvements?
- GitHub: https://github.com/openclaw/openclaw/issues
- Pull requests welcome!
Quick Commands Reference:
# Status
sudo launchctl print system/ai.openclaw.gateway
# Restart
sudo launchctl kickstart -k system/ai.openclaw.gateway
# Logs
tail -f ~/.openclaw/logs/gateway.log
# Uninstall
sudo launchctl bootout system/ai.openclaw.gateway
sudo rm /Library/LaunchDaemons/ai.openclaw.gateway.plist
Files
4 totalComments
Loading comments…
