In macOS, process lifecycle management, background automation, and service scheduling are governed by launchd. Designed by Apple to consolidate and replace legacy Unix facilities—including init, cron, inetd, and rc.local—launchd serves as the primary root process (PID 1) across the operating system. To inspect, load, trigger, and debug these background tasks from the Terminal, system administrators and power users interface directly with launchctl. Understanding how to manage LaunchDaemons and LaunchAgents via modern launchctl subcommands is fundamental to managing system services and diagnosing rogue background processes in macOS.
Distinguishing LaunchDaemons from LaunchAgents
Background tasks in macOS are declared through XML Property List (.plist) files and fall into two distinct execution tiers: LaunchDaemons and LaunchAgents. The table below delineates the architectural differences between them:
| Execution Tier | Filesystem Storage Directory | Security Context | Operational Lifecycle |
|---|---|---|---|
| System LaunchDaemons | /System/Library/LaunchDaemons/ |
Root (System-managed, SIP protected) | Boots with macOS kernel; zero user GUI interaction. |
| Administrator LaunchDaemons | /Library/LaunchDaemons/ |
Root or designated system user | Runs at startup before login; headless background background services. |
| Global LaunchAgents | /Library/LaunchAgents/ |
Active logged-in user | Initialises when any user logs in; has access to the user GUI window server. |
| User-Specific LaunchAgents | ~/Library/LaunchAgents/ |
Specific target user account | Initialises strictly when that particular user logs in; personal scripts and user utilities. |
Modern vs. Legacy launchctl Subcommands
Older macOS documentation frequently references legacy commands such as launchctl load and launchctl unload. In modern releases of macOS (macOS Monterey, Ventura, Sonoma, and Sequoia), Apple deprecated these in favour of domain-scoped targets utilizing bootstrap, bootout, kickstart, and print.
| Modern Subcommand | Target Domain Scope | Operational Function | Replaces Legacy Command |
|---|---|---|---|
launchctl bootstrap <domain> <path> |
system or gui/<uid> |
Loads and registers a service definition into the specified execution domain. | launchctl load |
launchctl bootout <domain> <path> |
system or gui/<uid> |
Unregisters and terminates a running service from the domain. | launchctl unload |
launchctl kickstart -k <service-target> |
system/<label> or gui/<uid>/<label> |
Forces an immediate process restart or execution trigger. | launchctl start |
launchctl print <domain> |
system or gui/<uid> |
Prints real-time diagnostic telemetry, exit codes, and environment variables. | launchctl list |
Querying and Inspecting Active Services
Before altering any system configuration, identify the user domain ID and inspect existing loaded services.
1. Identifying the User Domain ID
User-level agents execute inside the GUI domain mapped to your user ID (UID). Retrieve your current UID with:
id -u
(Typically 501 for the primary administrative user account).
2. Inspecting Domain Services with launchctl print
To inspect all currently registered user LaunchAgents:
launchctl print gui/$(id -u)
To inspect detailed diagnostic telemetry for a specific individual service (such as an internal backup utility labelled com.company.backup):
launchctl print gui/$(id -u)/com.company.backup
This command outputs the current PID, binary program path, environment variables, execution limits, and the last process exit code (where 0 indicates a clean exit and non-zero values highlight runtime crashes).
Authoring a Custom LaunchAgent Property List
To automate a script on a schedule or ensure an internal service restarts upon unexpected termination, create a property list definition.
1. Creating the .plist File
Navigate to your user agents directory and create a new property list:
nano ~/Library/LaunchAgents/com.user.maintenance.plist
Populate the file with the following structured XML configuration:
<?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>
<!-- Unique identifier label -->
<key>Label</key>
<string>com.user.maintenance</string>
<!-- Binary executable and arguments -->
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/python3</string>
<string>/Users/admin/scripts/cleanup.py</string>
</array>
<!-- Execution triggers -->
<key>RunAtLoad</key>
<true/>
<!-- Repeat interval specified in seconds (3600 = 1 hour) -->
<key>StartInterval</key>
<integer>3600</integer>
<!-- Standard input/output log paths -->
<key>StandardOutPath</key>
<string>/tmp/com.user.maintenance.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/com.user.maintenance.stderr.log</string>
</dict>
</plist>
2. Setting Proper File Permissions
macOS security modules will reject any property list with insecure permissions:
chmod 644 ~/Library/LaunchAgents/com.user.maintenance.plist
Loading and Managing Services via Modern launchctl
With the property list created and permissions verified, use modern domain targeting to control the service.
1. Bootstrapping the Service
To load and register the agent into the active user session:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.user.maintenance.plist
2. Forcing an Immediate Run with kickstart
Rather than waiting for the timer interval to elapse, trigger the process immediately to verify functionality:
launchctl kickstart -k gui/$(id -u)/com.user.maintenance
The -k switch terminates any currently running instance before launching a fresh execution.
3. Booting Out (Unloading) a Service
When updating a script or decommissioning a task, unregister it from memory:
launchctl bootout gui/$(id -u)/com.user.maintenance
Managing System-Wide LaunchDaemons with Elevated Privileges
For system-wide background processes running as root (such as database listeners or web services), property lists must be placed in /Library/LaunchDaemons/ with root ownership:
sudo chown root:wheel /Library/LaunchDaemons/com.company.service.plist
sudo chmod 644 /Library/LaunchDaemons/com.company.service.plist
To load the service into the system domain:
sudo launchctl bootstrap system /Library/LaunchDaemons/com.company.service.plist
To verify that the system daemon is active and running:
sudo launchctl print system/com.company.service
Diagnosing and Troubleshooting launchd Failures
When a background task fails to execute or enters a continuous crash-restart loop, evaluate these diagnostic checkpoints:
- Exit Code 78 (EX_CONFIG): Indicates a permissions error. Ensure the target script or binary possesses executable permissions (
chmod +x /path/to/script) and that parent directories are accessible by the service account. - macOS Full Disk Access and TCC Restrictions: If a script fails to access directories like
~/Documents,~/Downloads, or removable disks, macOS Transparency, Consent, and Control (TCC) security has blocked access. You must grant Full Disk Access to the calling binary (e.g.,/bin/bashor/usr/local/bin/python3) within System Settings > Privacy & Security > Full Disk Access. - Inspecting Live System Logs: Filter Apple Unified Logging in Terminal to watch
launchdactivity in real time:log stream --predicate 'process == "launchd"' --level debug - Unlocking Corrupted Service States: If
bootstrapreturnsservice already loadedbutprintshows it missing, force an unregister using:launchctl bootout gui/$(id -u)/com.user.maintenance 2>/dev/null || true