launchd has been around for quite some time now, I was still using
crontab for scheduling some of my scripts until recently. Since
launchd LaunchAgents can do much more and don’t expect your computer to be running at all times, it’s time to start using them more 😎.
Let’s see how we can easily set up a LaunchAgent to run a Python script for the current user in regular intervals:
Write a plist for your agent
Unlike crontab jobs, LaunchAgents are written in (quite verbose) plist XML. We define a
Label according to the name of our agent and then go on describing how it should behave (which program to run, what arguments, when to run it, etc.).
Here’s is an example with the filename
de.davidhamann.my-program.plist which we will place in
The first interesting element to look at is
<key>Label</key>. It precedes a
string element which provides the unique name for our agent. This name is generally the same as our plist filename without the extension (both should follow reverse-domain style). The label is required and we need it to later start our agent.
Next, we define what program to run and what arguments to pass to it. In the example we will run Python3 from
/usr/local/bin and give it the path of our script to execute.
StartInterval defines in what interval we want the program to run and takes an integer expected to contain the number of seconds. In our case, we run my-program.py every 30 seconds.
Load and start the agent 🚀
LaunchAgents can be loaded, started and unloaded with
launchctl. To do so, we need to supply the ID of our target user and the plist file we just created. You can get your user-id via
id -u or
id -u <the-username>, respectively.
Assuming we are in
~/Library/LaunchAgents/ we can now load our agent by executing the following command:
launchctl bootstrap gui/<your-user-id> de.davidhamann.my-program.plist
And then kick-start it with (or just wait 😊):
launchctl kickstart -k gui/<your-user-id>/de.davidhamann.my-program
Note that we use the
Label in this command, not the plist filename. The
-k options means that the service will first be killed, if running (see
Our agent should now be active and starting the program every 30 seconds.
Note: we are setting up an agent for a user in a gui session. You may want to specify other targets, e.g.
system for system-wide services. See
man launchctl for more.
Unloading an agent is as easy as:
launchctl bootout gui/<your-user-id> de.davidhamann.my-program.plist
In other tutorials you may see the use of
launchctl start and
launchctl unload. While these commands still work, they are from a from a previous implementation of
launchd and are classified as “Legacy”.
In case you’re having trouble with an agent, you can either look into
system.log or direct the output to a desired location by adding two keys for output and errors to the
Make sure to
kickstart again after you make your change.
Debug key, though mentioned in the
launchd manual, is not supported anymore.
Other use cases
Another common use case is
StartCalendarInterval for starting a program at a given time. A nice property of this is, that it will handle sleep times. So
launchd would start the job the next time your computer wakes up and won’t just skip it like cron (it does not do that for
StartInterval, though I didn’t find that to be a problem for my cases).
Have a look at
man launchd.plist for many more options or visit developer.apple.com for a broader (but in parts outdated) overview.