<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kinjalk Bajpai</title>
    <description>The latest articles on Forem by Kinjalk Bajpai (@strawhat121).</description>
    <link>https://forem.com/strawhat121</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F750668%2F0b858422-7edb-4a06-9c6d-c0581cb0e795.jpeg</url>
      <title>Forem: Kinjalk Bajpai</title>
      <link>https://forem.com/strawhat121</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/strawhat121"/>
    <language>en</language>
    <item>
      <title>A Beginner’s Guide to Scheduling Jobs on macOS</title>
      <dc:creator>Kinjalk Bajpai</dc:creator>
      <pubDate>Thu, 11 Sep 2025 17:31:32 +0000</pubDate>
      <link>https://forem.com/strawhat121/a-beginners-guide-to-scheduling-jobs-on-macos-3062</link>
      <guid>https://forem.com/strawhat121/a-beginners-guide-to-scheduling-jobs-on-macos-3062</guid>
      <description>&lt;p&gt;So recently I made a Go app that removes the screenshots from the Desktop. It can be found here &lt;a&gt;Screenshot Remover&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I created this app because I often take a lot of screenshots, which quickly clutter up my Desktop. To keep things tidy, the script automatically removes any image on the Desktop that hasn’t been modified in the last 7 days. That gives me plenty of time to save any screenshots I actually need, while keeping the rest from piling up.&lt;/p&gt;

&lt;p&gt;Now the next question, how do I save run the executable daily at some specific time?&lt;/p&gt;

&lt;p&gt;The obvious solution is &lt;code&gt;cron&lt;/code&gt; as that is something known and used by everyone. But while researching, I found that Apple recommends using &lt;code&gt;launchd&lt;/code&gt; for timed jobs over &lt;code&gt;cron&lt;/code&gt; in OS X.&lt;/p&gt;

&lt;p&gt;So here are the steps to run an executable automatically daily at a specific time - &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a LaunchAgent (.plist) file
&lt;/h2&gt;

&lt;p&gt;So what is LaunchAgent and what is a plist file?&lt;/p&gt;

&lt;p&gt;In Mac OS, &lt;code&gt;launchd&lt;/code&gt; is the primary system which is used to manage system services and any processes.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;LaunchAgent&lt;/strong&gt; is a type of job which is managed by &lt;code&gt;launchd&lt;/code&gt;. In simple terms, a LaunchAgent is a process that is run on behalf of the user when that user is logged in.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.plist&lt;/code&gt; file stands for &lt;em&gt;Property List&lt;/em&gt;. It is a file format that is used by MacOS for storing structured data.&lt;/p&gt;

&lt;p&gt;In our context, a .plist file is the config file that contains information about everything &lt;code&gt;launchd&lt;/code&gt; needs to know to manage the job.&lt;/p&gt;

&lt;p&gt;With the above details clear, please create a .plist file with the name of &lt;code&gt;com.user.myjob.plist&lt;/code&gt; inside the ~/Library/LaunchAgents/ with any name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing the .plist File
&lt;/h2&gt;

&lt;p&gt;Now the next question, how do we use a plist file to trigger an executable?&lt;/p&gt;

&lt;p&gt;Turns out, we can write xml code inside a plist file. So let's do that. Paste the following code inside the file -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.user.myjob&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/path/to/your/executable&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Run every day at 7:30 AM --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;7&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StandardOutPath&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/tmp/myjob.out&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StandardErrorPath&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/tmp/myjob.err&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what does this code do? Lets walk through the important stuff line by line. First we have this -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code specifies the DTD for a property list and tells macOS to interpret this XML as a plist file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a dictionary. In plist files, this is a key-value map similar to JSON objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Label&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.user.myjob&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Label&lt;/strong&gt; is basically a unique identifier for this job. Kinda like the job's internal id.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ProgramArguments&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/path/to/your/executable&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Program Arguments&lt;/strong&gt; defines what program needs to be executed. An array is used here because sometimes someone needs to provides multiple arguments. In our case, we only need to provide the full path of where the executable is stored.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StartCalendarInterval&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Hour&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;7&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;Minute&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;30&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines when the job should run. In our case, it is set to run at 7:30 AM daily. This dict allows us to specify multiple fields like Hour, Minute, Weekday, Month etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StandardOutPath&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/tmp/myjob.out&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;StandardErrorPath&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;/tmp/myjob.err&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How can we verify if this is running as expected or encountering errors? We can do that using keys to redirect the program's output.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;StandardOutPath&lt;/code&gt; is used for normal output (&lt;code&gt;stdout&lt;/code&gt;) and the &lt;code&gt;StandardErrPath&lt;/code&gt; is used for the error messages (&lt;code&gt;stderr&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;So once the job is run, we can check out &lt;code&gt;/tmp/myjob.out&lt;/code&gt; and &lt;code&gt;/tmp/myjob.err&lt;/code&gt; to debug.&lt;/p&gt;

&lt;p&gt;So now we have the plist ready to run the job. But we still need one more step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load the job with launchctl
&lt;/h2&gt;

&lt;p&gt;So we have the plist file, but we still need to inform &lt;code&gt;launchd&lt;/code&gt; to load it. Thankfully it can be done with just one command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl load ~/Library/LaunchAgents/com.user.myjob.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after this you can use the following command to verify that it has loaded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;launchctl list | &lt;span class="nb"&gt;grep &lt;/span&gt;com.user.myjob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! Your job is scheduled successfully. We have configured a plist file to run an executable daily or whenever we want.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
