Build1 distinct publisher3 min readPublished
Apple has been prying cron loose from the user session since Ventura. The result is a Sequoia Mac that can print every crontab entry with nothing behind it, so the honest test is whether launchctl will admit to a cron job at all.
The Engineer · Build desk
build
26 launchd jobs, one log directory: how scheduled automation rots quietly1 distinct publisher
build
Five days of silence: a launchd job that only failed when a human ran it1 distinct publisher
build
A quota error looks like a retry, so 15 jobs idled six hours unnoticed1 distinct publisher
build
A cost monitor overcounted 4.9x, then went dark for a week when set -e did its job1 distinct publisher
Compiled by The EngineerSomething wrong?How this is made
The spool file is the only thing `crontab -l` consults, so it keeps answering correctly long after the daemon that reads it stops being loaded [2][3]. That is the whole trap. The author of the dev.to post noticed at 8:00, when the brief did not arrive, because nothing else was going to tell him [1].
The plist he published is worth reading closely, because it shows what a line-by-line translation loses. `ProgramArguments` is an argv array, and in the production file the first element is a quota-guard script, followed by `--job`, a label, a bare `--`, then `/bin/bash` and the job script [14]. There, the shell shows up as an explicit argument, called only when the array is invoked. Any crontab line with a pipe, an `&&`, or a redirect is not one argv away from working, and a 97-line parser [5] can only carry over the parts that fit.
Then the schedule. The post says the job runs twice, at 8:00 and 10:30, using two `StartCalendarInterval` dictionaries, and offers `0 8,10 * * *` as the cron form [12]. That expression fires at 8:00 and 10:00 [13]. The comma list in a cron field is a cross-product over minute and hour; a dictionary array is a set of explicit points. A generator that maps one to the other naively will move a job by thirty minutes and never mention it, which is the same failure class the migration was supposed to end.
The verbosity argument holds up better than it reads. Ten jobs at 20 to 30 lines each is 200 to 300 lines of XML to write and review, against 97 lines of generator [19]. The generator is the smaller artifact and the only one you can meaningfully diff. It still emits files that need a read: `RunAtLoad` is true in the production plist [16], so bootstrapping the agent to test it also runs the job for real.
Which leaves the part the post identifies but does not solve. `StandardOutPath` and `StandardErrorPath` both point at one log file [17], and launchd will restart a crashed daemon and run a calendar job after the machine wakes [7]. Every one of those helps a job that ran. A log only tells you what happened if you go looking for it, and none of these mechanisms had anything to report because the agent had never been loaded since the OS upgrade, which is exactly the state the machine was in.
So the alert has to be built to assert success, because nothing reports failure on its own. The job touches a timestamp or pings an external dead-man, and something that is not on that Mac complains when the touch is late. In my context, anything with money or data behind it does not live on a laptop scheduler at all; what stays gets a plist plus an off-box check. The author's rebuilt income ran through this crontab [18], and the cost of the check is one curl per job.
Apple keeps cron on disk as historical compatibility while recommending launchd [6], which is a polite way of saying the binary is furniture. `/usr/sbin/cron` present and `launchctl list | grep cron` empty is the documented normal state on Sequoia and Tahoe [3]. Treat the presence of the binary as decoration and the launchctl output as the fact.
Ranked by verification strength, evidence, and original report placement.
The author's automated 8:00 daily brief simply did not arrive one morning, with no error, no alert and nothing else to signal the failure.
Right after upgrading to macOS Sequoia (15.x) nothing appeared to have changed: running crontab -l still showed every entry, but the cron daemon was not running.
The author's fix was a 97-line shell script that parses crontab line by line and auto-generates launchd plists.
On macOS, process launching and management belongs to launchd (PID 1); cron survives only as historical compatibility, and what Apple recommends is job management via launchd.
launchd handles automatic restarts when a daemon crashes, automatic execution after wake for jobs scheduled while the machine was asleep, direct redirection of stdout/stderr to files, and explicit injection of environment variables, all declaratively in a single plist.
A cron entry is one line such as */5 * * * * cmd, while a launchd plist becomes 20 to 30 lines of XML, which the author calls the biggest psychological barrier to migrating.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 31, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Verbatim config, anecdotal diagnosis
Two very different grades of evidence sit side by side here. The plist is reproduced from the real file, so every configuration detail — the pinned PATH, Nice 10, both output streams into one log — can be read and checked. The claim the headline rests on, that Apple has been prying cron out of the user session since Ventura and that a Sequoia Mac will happily list jobs it no longer runs, has exactly one upgrade on one machine behind it and no Apple documentation. The post also mis-states its own example: '0 8,10 * * *' cannot produce the 10:30 run its plist performs.
A sample of one Mac
Real but tiny. There is a genuine deployment — a named launchd job running the author's daily brief, with a quota guard in front of it and a log file behind it — and that is the entire footprint. No other user, machine or organisation reports the Sequoia symptom or the migration script, and the generator is a personal file at a personal path rather than anything published or installed elsewhere.
Platform verdict from a single upgrade
The framing outruns what was actually established. 'A macOS update quietly disabled the cron daemon' is a statement about Apple; what was observed is that after an upgrade, one machine's jobs stopped and launchctl mentioned no cron — a finding equally consistent with several less dramatic explanations the post never rules out, since /usr/sbin/cron was still sitting there. Overshoot in the other direction, too: the practical warnings that would actually bite a reader, the unsupported */N schedules and the double-firing until crontab lines are removed, are relegated to a diagram while the yen figures get the first sentence.
The autonomous-income story is the product
This is a first-person post whose authority comes from the ¥1.2M/month setup it opens with, published on a platform where that story earns attention. A dramatic near-miss — the income machine going silent overnight — makes the setup more credible, not less, and the tidy payoff line (90 minutes of work buying back 30 hours a year) reads like a pitch rather than a measurement. None of that makes the plist wrong; it does explain why the platform-blaming version of the diagnosis was the one that got written.
Full text, single witness
We can be quite sure what this reporting says and where it breaks, because the configuration is quoted and the schedule error is checkable on the page. We cannot be sure about the thing readers will act on. Whether Sequoia really stops running cron for everyone would need Apple's own words or a second machine, and neither is here.