macOS launchd — SKILL.md

Raw skill file that agents receive when using this skill

Download
---
name: "macOS launchd"
description: "Skill for macOS launchd — auto-generated from documentation"
version: "1.0.0"
author: "skynet"
category: "ops"
agents: ["claude-code", "codex", "gemini"]
tags: ["launchd", "ops", "auto-generated"]
---

# macOS launchd

---
name: macOS launchd
description: Use this skill when managing system services, background processes, or scheduled tasks on macOS. Essential for automating scripts, managing daemons, and controlling system startup behavior.
metadata:
  author: skynet
  version: 1.0.0
category: ops
---

# macOS launchd Management

## Overview
launchd is macOS's unified service management framework that handles daemons, agents, and scheduled tasks. It replaces cron, xinetd, and other legacy process managers.

## Key Concepts
- **LaunchDaemons**: System-wide services (run as root)
- **LaunchAgents**: Per-user services (run as user)
- **Property Lists (plist)**: XML configuration files defining jobs

## Location Structure
```
/System/Library/LaunchDaemons/     # System daemons (Apple)
/System/Library/LaunchAgents/      # System agents (Apple)
/Library/LaunchDaemons/            # Custom system daemons (root)
/Library/LaunchAgents/             # Custom system agents (all users)
~/Library/LaunchAgents/            # User-specific agents
```

## Essential Commands

### Service Management
```bash
# Load service
sudo launchctl load /Library/LaunchDaemons/com.example.service.plist

# Unload service
sudo launchctl unload /Library/LaunchDaemons/com.example.service.plist

# Start service immediately
sudo launchctl start com.example.service

# Stop running service
sudo launchctl stop com.example.service

# List all loaded services
launchctl list

# Check specific service status
launchctl list | grep com.example.service

# Get service info
launchctl print system/com.example.service
```

### Bootstrap Management (macOS 10.11+)
```bash
# Load into system domain
sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.service.plist

# Load into user domain
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.agent.plist

# Remove from domain
sudo launchctl bootout system/com.example.service

# Enable service
sudo launchctl enable system/com.example.service

# Disable service
sudo launchctl disable system/com.example.service
```

## Creating Basic plist Files

### Simple Daemon Template
```xml
<?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>com.example.mydaemon</string>
    <key>Program</key>
    <string>/usr/local/bin/mydaemon</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/var/log/mydaemon.log</string>
    <key>StandardErrorPath</key>
    <string>/var/log/mydaemon.error.log</string>
</dict>
</plist>
```

### Scheduled Task (Cron Replacement)
```xml
<?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>com.example.backup</string>
    <key>Program</key>
    <string>/usr/local/bin/backup.sh</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>2</integer>
        <key>Minute</key>
        <integer>30</integer>
    </dict>
    <key>StandardOutPath</key>
    <string>/var/log/backup.log</string>
</dict>
</plist>
```

### Web Service with Environment Variables
```xml
<?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>com.example.webservice</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/bin/node</string>
        <string>/opt/myapp/server.js</string>
    </array>
    <key>WorkingDirectory</key>
    <string>/opt/myapp</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>NODE_ENV</key>
        <string>production</string>
        <key>PORT</key>
        <string>3000</string>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>UserName</key>
    <string>_www</string>
    <key>GroupName</key>
    <string>_www</string>
</dict>
</plist>
```

## Common Configuration Keys

### Essential Keys
- `Label`: Unique identifier (reverse domain notation)
- `Program`: Path to executable
- `ProgramArguments`: Array of program and arguments
- `RunAtLoad`: Start when loaded
- `KeepAlive`: Restart if crashes

### Scheduling Keys
- `StartCalendarInterval`: Specific times/dates
- `StartInterval`: Regular intervals (seconds)
- `WatchPaths`: Monitor file/directory changes

### Resource Keys
- `WorkingDirectory`: Set working directory
- `UserName`/`GroupName`: Run as specific user/group
- `EnvironmentVariables`: Set environment

### Logging Keys
- `StandardOutPath`: Redirect stdout
- `StandardErrorPath`: Redirect stderr
- `StandardInPath`: Redirect stdin

## Decision Tree: Service Type Selection

```
Need to run a service?
├── System-wide (all users)?
│   ├── Background service → LaunchDaemon (/Library/LaunchDaemons/)
│   └── User interaction needed → LaunchAgent (/Library/LaunchAgents/)
└── Single user only?
    └── User-specific service → LaunchAgent (~/Library/LaunchAgents/)

When to start?
├── System boot → RunAtLoad + LaunchDaemon
├── User login → RunAtLoad + LaunchAgent
├── Scheduled time → StartCalendarInterval
├── File changes → WatchPaths
└── On demand → omit RunAtLoad
```

## Workflow: Creating and Installing a Service

```bash
# 1. Create plist file
sudo vim /Library/LaunchDaemons/com.example.myservice.plist

# 2. Set proper permissions
sudo chown root:wheel /Library/LaunchDaemons/com.example.myservice.plist
sudo chmod 644 /Library/LaunchDaemons/com.example.myservice.plist

# 3. Validate plist syntax
plutil -lint /Library/LaunchDaemons/com.example.myservice.plist

# 4. Load service
sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.myservice.plist

# 5. Enable service
sudo launchctl enable system/com.example.myservice

# 6. Start service
sudo launchctl kickstart system/com.example.myservice

# 7. Verify status
sudo launchctl print system/com.example.myservice
```

## Debugging and Monitoring

### Check Service Status
```bash
# List all services with status
launchctl list | grep -E "(PID|Label)"

# Detailed service info
launchctl print system/com.example.service

# Check if service is enabled
launchctl print-disabled system | grep com.example.service

# View service logs
tail -f /var/log/system.log | grep com.example.service
```

### Console Logs
```bash
# Real-time system logs
log stream --predicate 'subsystem == "com.apple.launchd"'

# Service-specific logs
log show --predicate 'subsystem == "com.example.service"' --last 1h
```

## Troubleshooting

### Common Error: "Operation not permitted"
```
Error: Bootstrap failed: 5: Input/output error
```
**Fix**: Check plist ownership and permissions
```bash
sudo chown root:wheel /Library/LaunchDaemons/com.example.service.plist
sudo chmod 644 /Library/LaunchDaemons/com.example.service.plist
```

### Common Error: "Could not find specified service"
```
Error: Could not find service "com.example.service" in domain for system
```
**Fix**: Service not loaded or wrong domain
```bash
# List what's actually loaded
sudo launchctl print system | grep com.example
# Bootstrap if missing
sudo launchctl bootstrap system /path/to/service.plist
```

### Common Error: Service keeps crashing
**Check**: Console.app → System Reports → LaunchDaemons
**Fix**: Add debugging to plist
```xml
<key>StandardOutPath</key>
<string>/tmp/myservice.log</string>
<key>StandardErrorPath</key>
<string>/tmp/myservice.error.log</string>
```

### Common Error: "Invalid property list"
**Check**: Syntax validation
```bash
plutil -lint /path/to/service.plist
xmllint --format /path/to/service.plist
```

### Service won't start at boot
**Fix**: Verify RunAtLoad and check system integrity
```bash
# Check if disabled
launchctl print-disabled system

# Re-enable if needed
sudo launchctl enable system/com.example.service
```

## Best Practices

### Security
- Use specific user accounts, not root when possible
- Set minimal file permissions (644 for plists)
- Validate all paths in plist files

### Reliability
- Always include StandardOutPath and StandardErrorPath
- Use KeepAlive judiciously (can mask real problems)
- Test services manually before installing

### Maintenance
- Use consistent naming (reverse domain notation)
- Document environment requirements
- Version control your plist files
- Monitor logs regularly

curl -s https://skills.skynet.ceo/api/skills/launchd/skill.md