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).