/home/josephspurrier

When Cron Jobs Disappear: MacOS Sleep

I had Claude develop a build service that handles incoming webhooks from GitHub and then triggers pipelines on my Mac. I also had it build in cron capabilities to run a script in the middle of the night. The first morning I checked the logs and everything looked fine. The second morning, nothing. Third morning, it ran. Fourth, nothing again. No errors, no failed attempts. The job just didn’t exist on those nights.

Claude checked the crontab syntax, the script permissions, the paths. Everything was correct. The job would run perfectly if I triggered it manually. It just wouldn’t fire reliably at 3 AM.

The Problem Is Sleep

Once I told Claude to stop guessing at the problem and give me a definitive solution, the answer turned out to be a little sneakier than I thought. macOS aggressively puts your Mac to sleep when it’s idle. When a Mac is asleep, cron doesn’t run. Overnight it cycles through brief DarkWake/Sleep intervals, staying awake only long enough for background tasks like email fetch before going back to sleep. If your scheduled time passes while the machine is asleep, the job is simply skipped. It doesn’t queue up or retry when the machine wakes.

This is different from a Linux server that stays powered on 247. On a Mac, especially a laptop, the default power management is designed to save energy, not to honor your crontab.

Working Around It in Cron

The simplest workaround is to schedule jobs for times when the machine is likely awake. If you’re at your desk from 9 to 5, schedule your cron jobs during those hours. Instead of running a backup at 3 AM, run it at noon or at startup.

You can also use @reboot in your crontab to run a job every time the machine starts up:

@reboot /path/to/your/script.sh

This won’t help with jobs that need to run on a strict schedule, but for things like “run this once a day and I don’t care exactly when,” it can be enough.

Another approach is to use a wrapper script that checks whether the job has already run today before executing. launchd, which is Apple’s replacement for cron, has a StartCalendarInterval option that will run a missed job the next time the system is awake, which is closer to what most people actually want. But if you’re already comfortable with cron and don’t want to rewrite everything as a plist, there are two system-level tools worth knowing about.

pmset: Controlling When Your Mac Sleeps and Wakes

pmset is the macOS power management utility. It controls sleep behavior, wake schedules, and power assertions at the system level. You can use it to prevent sleep entirely or to schedule your Mac to wake up right before your cron job needs to run.

To check your current power settings:

pmset -g

To see all the details including scheduled wake events:

pmset -g sched

To schedule your Mac to wake up at a specific time every day:

sudo pmset repeat wakeorpoweron MTWRFSU 02:55:00

That tells the Mac to wake up at 2:55 AM every day of the week, giving it a few minutes to fully wake before your 3 AM cron job fires. The day codes are M (Monday), T (Tuesday), W (Wednesday), R (Thursday), F (Friday), S (Saturday), U (Sunday).

To cancel a scheduled wake:

sudo pmset repeat cancel

You can also prevent the Mac from sleeping entirely while it’s plugged in:

sudo pmset -c sleep 0

The -c flag targets charger (AC power) settings specifically. The sleep 0 means “never sleep.” To restore the default (e.g., sleep after 10 minutes):

sudo pmset -c sleep 10

pmset is powerful but it’s a system-wide setting. If you just want to keep the machine awake for a single command, there’s a lighter tool.

caffeinate: Keeping Your Mac Awake for a Single Task

The path I ultimately took was using caffeinate in my plist file for the service. caffeinate is a command-line utility that prevents macOS from sleeping for as long as it’s running. The name is exactly what it sounds like: it keeps your Mac caffeinated.

The simplest usage wraps your command:

caffeinate -s /path/to/your/script.sh

The -s flag prevents system sleep while on AC power. Your script runs, and as soon as it finishes, the Mac is free to sleep again.

Other useful flags:

caffeinate -i           # Prevent idle sleep (system stays awake)
caffeinate -d           # Prevent display sleep
caffeinate -s           # Prevent system sleep (on AC power)
caffeinate -t 3600      # Stay awake for 3600 seconds (1 hour), then allow sleep
caffeinate -w <pid>     # Stay awake as long as the given process is running

caffeinate prevents the machine from going to sleep but it doesn’t wake it up so caffeinate is most useful paired with pmset for scheduled wakes, or for jobs that run during hours when you know the machine is on.

Which Approach to Use

If you need a job to run at a specific time overnight, use pmset to schedule a wake event a few minutes before the job, and optionally wrap the command in caffeinate to make sure the machine doesn’t fall back asleep before it finishes.

If you just need a job to run “once a day, whenever,” switch to launchd with StartCalendarInterval or use @reboot in cron with a check for whether the job already ran today.

If you’re running long tasks during the day and don’t want the Mac sleeping mid-process, caffeinate wrapping the command is the cleanest solution.

The underlying lesson is that macOS is not a server. It’s an operating system built for laptops and desktops that people close, unplug, and walk away from. If you’re using it like a server, you need to set it up accordingly.

#macos #cron #bash