Skip to content

revive: start the launch agents macOS skipped at login

The problem

Since macOS 13, a launch agent in ~/Library/LaunchAgents is started at login only if it is switched on under Login Items > "Allow in the Background". When the switch is off, nothing says so: the backup or sync job just stops after the next restart. The list shows developer names, not labels, so home-made agents are hard to find there, and new ones start off.

The method

The switch governs what macOS loads at login. It does not stop a running process from loading an agent with launchctl bootstrap. So allow one agent, and have it load the rest from a list:

launchctl print gui/$(id -u)/com.example.backup       # loaded? leave it
launchctl print-disabled gui/$(id -u)                 # switched off? leave it off
plutil -lint -s com.example.backup.plist              # parses? then
launchctl bootstrap gui/$(id -u) com.example.backup.plist

Reading launchd's own off-list means a deliberate launchctl disable is never undone. Plists are matched by their Label key, not only the file name. A failed bootstrap followed by "now loaded" is a lost race, not an error. The reviver never starts its own label, and REVIVE_SKIP globs keep a family of agents manual.

Sample

./revive -n                       # what it would start
./revive                          # start them; exit 1 if one could not be
./revive plist com.example.revive > ~/Library/LaunchAgents/com.example.revive.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.revive.plist
# then allow com.example.revive under Login Items; it runs at login and every 10 min

Limits

  • The revive agent itself must be allowed, or nothing runs at login.
  • User agents only (gui/<uid>); system daemons need root.
  • Loaded is not healthy: an agent that keeps failing is left alone.

Code

methods/revive/ in the methods repository: the script, a README and a 24-check test with a stand-in launchctl (REVIVE_REAL=1 adds three checks against the real launchd with a throwaway agent).