MacOS – launchctl error “nothing found to load” when run via cron

launchdmacosterminal

On OS X 10.9, I'm running a script (e.g., ~/bin/run.sh) via my user crontab (added using crontab -e). This script, under some specific conditions (not related to this question), will run the following command to launch a menubar application:

launchctl load /Library/LaunchAgents/com.opendns.osx.RoamingClientMenubar.plist

When I run this command (either ~/bin/run.sh or the launchctl statement above directly) from the command line normally, the menubar item launches fine.

When this command is run via crontab (again, directly or via ~/bin/run.sh), I receive the message nothing found to load in the cron output (in my mail).

Question: why does this fail when run via cron but not when run on the command line?

I have tried executing it via cron in the simplest way possible:

* * * * * launchctl load /Library/LaunchAgents/com.opendns.osx.RoamingClientMenubar.plist

This doesn't work (I get nothing found to load).

I have tried emulating the cron environment:

  1. Capturing the cron environment by having this execute in cron:

    env > ~/cronenv
    
  2. Then opening a shell with this environment:

    env - `cat ~/cronenv` /bin/sh
    
  3. And finally running the command:

    launchctl load /Library/LaunchAgents/com.opendns.osx.RoamingClientMenubar.plist
    

It runs find in these conditions (I wouldn't expect it to, if something in the cron environment is the culprit).

I have tried running it from crontab as sudo. Nope (nothing found to load).

I have tried running it from crontab with launchctl load -F and launchctl load -w. No luck (nothing found to load).

Permissions on the plist file are:

-rw-r--r--  1 root  wheel  561 Apr 13 20:55 /Library/LaunchAgents/com.opendns.osx.RoamingClientMenubar.plist

What's going on?

(BTW, I know it may seem silly to run a script with a launchctl job from within cron, but because it is run within a shell script it's prevented from being a 100% launchctl-controlled process.)

Update: as requested here is the script that is being run (I've been calling it ~/bin/run.sh), the line in question being #29, and here is the contents of the plist.

Update: the specific solution that works for me, based on @mateusz-szlosek's suggestion to use bsexec, looks like this:

sudo launchctl bsexec "$(ps -axwww | grep Dock | grep -v grep | awk {'print $1'};)" sudo -u $USER launchctl load /Library/LaunchAgents/com.opendns.osx.RoamingClientMenubar.plist

The first sudo is required otherwise the error Couldn't switch to new bootstrap port occurs. The second sudo is to execute launchctl as $USER. The first argument to bsexec is a parent process ID who's context will be used to launch the new process. $(ps -axwww | grep Dock | grep -v grep | awk {'print $1'};) returns the pid of the Dock process, which loads somewhat early in the launchd hierarchy, but under user context.

Best Answer

The problem is execution context and mach bootstrap in OS X (more info on Apple documentation worth reading). You need to simulate execution from proper context. In OS X it's done using launchctl bsexec command. From TheDarkKnight's answer to Starting/stopping a launchd agent for all users with GUI sessions you have:

Unload Agents

#!/bin/bash
for id in `ps aux | grep -v grep | grep MyAgent | awk {'print $2'}`
do
    launchctl bsexec $id launchctl unload /Library/LaunchAgents/myAgent.plist
done

Replace MyAgent with the name of your Launch Agent.

Load Agents

#!/bin/bash
for pid_uid in $(ps -axo pid,uid,args | grep -i "[l]oginwindow.app" | awk '{print $1 "," $2}'); do

    pid=$(echo $pid_uid | cut -d, -f1)
    uid=$(echo $pid_uid | cut -d, -f2)

    launchctl bsexec "$pid" chroot -u "$uid" / launchctl load /Library/LaunchAgents/myAgent.plist
done

On OS X 10.11 and Later

From the comment by vrrathod about OS X 10.11, El Capitain, use:

launchctl bootstrap gui/$uid /Library/LaunchAgents/myAgent.plist