Troubleshooting
Common issues, error messages, launchd gotchas, and diagnostic commands.
Common Issues
Agent not loading
Symptoms: Job shows as "Unloaded" in the dashboard after saving. launchctl list does not show the job label.
Possible causes:
- Plist file permissions are wrong. launchd silently ignores plist files with incorrect permissions. The file must be owned by the current user and have permissions
644or stricter.# Check permissions ls -la ~/Library/LaunchAgents/com.launchpad.myjob.plist # Fix if needed chmod 644 ~/Library/LaunchAgents/com.launchpad.myjob.plist - Malformed plist XML. Validate the plist with
plutil:plutil -lint ~/Library/LaunchAgents/com.launchpad.myjob.plist - Duplicate label. Another agent with the same label is already registered. Check with
launchctl list | grep myjob.
Permission denied errors
Symptoms: Job loads but exits immediately with exit code 78 or 1. Stderr shows "Permission denied".
Solution: Ensure the executable has execute permissions:
chmod +x /path/to/your/script.sh
Also check that the script's shebang line is correct (e.g., #!/bin/bash or #!/usr/bin/env python3).
Schedule not firing
Symptoms: Job is loaded and enabled but never runs at the expected time.
Possible causes:
- Mac was asleep.
StartCalendarIntervaldoes not fire while the machine is asleep. If the Mac was sleeping at the scheduled time, the job runs once when the Mac wakes up (launchd coalesces missed runs). - Using
StartIntervalandStartCalendarIntervaltogether. This has undefined behavior. Use only one. LaunchPad prevents this at the form level, but manually-edited plists may have this issue. - Incorrect
Weekdayvalues. In launchd, Sunday is0and Saturday is6. This is different from cron on some systems where Sunday can be7. - ThrottleInterval throttling. launchd enforces a minimum 10-second interval between job launches. If your schedule would trigger more frequently, launchd silently throttles it.
Log file missing
Symptoms: Job runs but log viewer shows "No output captured".
Possible causes:
- No
StandardOutPath/StandardErrorPathin the plist. LaunchPad auto-defaults these for managed jobs, but external jobs may not have them set. Without these keys, stdout/stderr goes to/dev/null. - Log directory doesn't exist. LaunchPad creates
~/Library/Logs/LaunchPad/automatically, but if the directory was deleted, log writes will fail silently. - Disk full. If the disk is full, log files cannot be written. Check available space with
df -h.
Dashboard port conflict
Symptoms: LaunchPad fails to start with "EADDRINUSE" error.
Solution: Another process is using port 24680. Find it:
# Find the process using port 24680
lsof -i :24680
# Or change the port via environment variable
LAUNCHPAD_PORT=24681 pnpm start
launchd Gotchas
launchd has been stable for 15+ years, but it has quirks that trip up even experienced macOS users.
Domain targets (modern vs. legacy syntax)
Apple has deprecated the load/unload subcommands in favor of the domain-target syntax. Both still work, but you may see deprecation warnings.
# Legacy (still works, prints deprecation warning)
launchctl load ~/Library/LaunchAgents/com.launchpad.myjob.plist
launchctl unload ~/Library/LaunchAgents/com.launchpad.myjob.plist
# Modern (recommended)
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.launchpad.myjob.plist
launchctl bootout gui/$(id -u)/com.launchpad.myjob
# Check if a job is loaded (modern)
launchctl print gui/$(id -u)/com.launchpad.myjob
Unload before reload
When editing a plist, you must unload (bootout) the job before loading (bootstrapping) the updated version. Loading a new plist without unloading the old one causes undefined behavior -- sometimes the old version keeps running, sometimes launchd returns a "service already loaded" error.
LaunchPad handles this automatically during job edits.
ProgramArguments vs. Program
The ProgramArguments key takes an array where the first element is the executable and subsequent elements are arguments. The Program key takes a single string (the executable path) with no arguments. If you use Program, you cannot pass arguments.
Using both Program and ProgramArguments in the same plist causes confusing behavior. Program overrides ProgramArguments[0] as the executable, but the arguments from ProgramArguments are still passed. LaunchPad always uses ProgramArguments for consistency.
StartCalendarInterval array wrapping
A single StartCalendarInterval dict works fine for one schedule. But if you want multiple schedules (e.g., weekdays at 9 AM), each day needs its own dict in an array. Forgetting the array wrapper causes only the first schedule to fire.
Environment differences from Terminal
LaunchAgents run with a minimal environment -- not your shell profile (.zshrc, .bashrc). This means:
PATHis minimal (/usr/bin:/bin:/usr/sbin:/sbin)- Homebrew paths (
/opt/homebrew/bin) are not included - Custom environment variables from your shell are not available
Fix: Use absolute paths for executables (e.g., /opt/homebrew/bin/python3 instead of python3) or set EnvironmentVariables in the plist with a custom PATH.
Diagnostic Commands
Useful terminal commands for debugging LaunchAgent issues.
# List all loaded user LaunchAgents
launchctl list
# Get detailed info about a specific job
launchctl print gui/$(id -u)/com.launchpad.myjob
# Check if a plist is valid XML
plutil -lint ~/Library/LaunchAgents/com.launchpad.myjob.plist
# Convert plist to readable JSON for inspection
plutil -convert json -o - ~/Library/LaunchAgents/com.launchpad.myjob.plist | python3 -m json.tool
# List all plist files in user LaunchAgents directory
ls -la ~/Library/LaunchAgents/
# Check system log for launchd errors
log show --predicate 'subsystem == "com.apple.xpc.launchd"' --last 5m
# Check what port LaunchPad is running on
lsof -i :24680
# View LaunchPad server logs
tail -f ~/Library/Logs/LaunchPad/server.log
# Check SQLite database integrity
sqlite3 ~/.launchpad/launchpad.db "PRAGMA integrity_check;"
# Count total jobs in database
sqlite3 ~/.launchpad/launchpad.db "SELECT COUNT(*) FROM jobs WHERE archived_at IS NULL;"
Error Messages
| Error | Cause | Fix |
|---|---|---|
EADDRINUSE | Port 24680 is in use | Kill the existing process or change the port |
DUPLICATE_LABEL | A job with this label already exists | Choose a different label or edit the existing job |
PLIST_WRITE_ERROR | Cannot write to ~/Library/LaunchAgents/ | Check directory permissions and disk space |
LAUNCHCTL_ERROR | launchctl command failed | Check stderr output in the dashboard for details |
SCHEDULE_CONFLICT | Both interval and calendar schedule set | Use only StartInterval or StartCalendarInterval, not both |
READ_ONLY_JOB | Attempted to modify a system/external job | Only dashboard-created jobs can be edited |
AUTH_REQUIRED | Touch ID verification needed | Authenticate with Touch ID or disable in Settings |
| Exit code 78 | Configuration error in plist | Validate plist with plutil -lint |
| Exit code 126 | Permission denied on executable | chmod +x the script |
| Exit code 127 | Command not found | Use absolute path to executable |
Frequently Asked Questions
/Library/LaunchAgents/ are displayed as read-only for visibility.127.0.0.1 and is inaccessible from the network. There are no cloud services, no telemetry, no analytics, no external API calls. All data stays in a local SQLite file at ~/.launchpad/launchpad.db.LAUNCHPAD_PORT environment variable before starting the server, or change it in Settings within the dashboard. If you're using the auto-start LaunchAgent, update the EnvironmentVariables section of ~/Library/LaunchAgents/com.launchpad.server.plist.PATH doesn't include Homebrew, pyenv, nvm, or other tools. Use absolute paths to executables (e.g., /opt/homebrew/bin/python3) and set any needed environment variables explicitly in the LaunchPad job configuration.- Unload the LaunchPad server:
launchctl bootout gui/$(id -u)/com.launchpad.server - Unload the menubar:
launchctl bootout gui/$(id -u)/com.launchpad.menubar - Remove LaunchPad plists:
rm ~/Library/LaunchAgents/com.launchpad.server.plist ~/Library/LaunchAgents/com.launchpad.menubar.plist - Remove the database:
rm -rf ~/.launchpad/ - Remove logs:
rm -rf ~/Library/Logs/LaunchPad/ - If installed via Homebrew:
brew uninstall launchpad
Your managed LaunchAgent plists (the jobs you created) remain in ~/Library/LaunchAgents/ and continue to work independently of LaunchPad.