As (somewhat*) recently mentioned I switched back to Taskwarrior. On my server I just re-enabled my cronjob to rollover incomplete tasks each day. On my work macOS machine I was doing this manually to start with until I found time to figure out launchd (it had been a few years). I ended up with this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>org.taskwarrior.rollover</string>
	<key>Program</key>
	<string>/Users/simon/bin/task_rollover</string>
	<key>StandardOutPath</key>
	<string>/tmp/task.out</string>
	<key>StandardErrorPath</key>
	<string>/tmp/task.err</string>
	<key>WorkingDirectory</key>
	<string>/Users/simon</string>
	<key>UserName</key>
	<string>simon</string>
	<key>RunAtLoad</key>
	<false />
	<key>StartCalendarInterval</key>
	<dict>
		<key>Hour</key>
		<integer>0</integer>
		<key>Minute</key>
		<integer>1</integer>
	</dict>
</dict>
</plist>

And not that I needed to for this case, but if you want to run at multiple times then you need to use array:

	<key>StartCalendarInterval</key>
	<array>
		<dict>
			<key>Hour</key>
			<integer>09</integer>
			<key>Minute</key>
			<integer>0</integer>
		</dict>
		<dict>
			<key>Hour</key>
			<integer>22</integer>
			<key>Minute</key>
			<integer>0</integer>
		</dict>
	</array>

(I needed this kind of setup for something else, but might as well include it in these notes/post).

Anyway, the script I was calling was just:

#!/bin/sh

/opt/pkg/bin/task $(/opt/pkg/bin/task rc:/Users/simon/.taskrc status:pending due.before:today ids) rc:/Users/simon/.taskrc rc.confirmation:no rc.bulk:100 modify due:today

I couldn’t figure out getting it working without a wrapper script.

Also, to get it to actually load and run properly I couldn’t use the (deprecated?) load/unload commands:

launchctl load ~/Library/LaunchAgents/org.taskwarrior.rollover.plist
launchctl unload ~/Library/LaunchAgents/org.taskwarrior.rollover.plist

I had to do:

launchctl enable user/501/~/Library/LaunchAgents/org.taskwarrior.rollover.plist
launchctl bootstrap gui/501 ~/Library/LaunchAgents/org.taskwarrior.rollover.plist

The 501 you can get from running id. Then you should see the launchagent listed via launchctl list.

To run it once:

launchctl kickstart gui/501/org.taskwarrior.rollover

To disable:

launchctl disable user/501/~/Library/LaunchAgents/org.taskwarrior.rollover.plist
launchctl bootout gui/501 ~/Library/LaunchAgents/org.taskwarrior.rollover.plist

You need to disable and re-enable if you make changes to the plist file. I like how Apple streamlined that from two unload and load commands to needing four commands. Thanks to stumbling across this Reddit post for those.

Also, the StandardOutPath and StandardErrorPath didn’t actually help at all with debugging why it wasn’t running so those can be removed.

Give me the simplicity of NetBSD’s cron any day.

* - I wrote this up ages ago and forgot to post it