No log file at all
While registering a new job on my monitoring board, I noticed something odd. The launchd job that scrapes store listings showed runs = 0 since it was loaded. Not failed runs. No runs. The log file had never been created.
This job refreshes sources/app_listings.json. That file is the only grounding source for the body text of my auto-published Threads and Facebook posts. The app descriptions quoted in posts, and the price check that decides whether a post may say "free", both come from it.
Do you have a job like this? One whose output another pipeline reads, while nobody checks when it last ran?
The job never threw an error. It never ran, so it never had the chance. And because it wasn't on the monitored list, nothing was deciding whether its silence was normal.
The cause: one Day key
The plist's StartCalendarInterval contained Day = 1. In launchd, Day means day of the month. So instead of running daily at 08:10, the job ran only on the 1st of each month.
StartCalendarInterval:
Day: 1 # 1st of the month
Hour: 8
Minute: 10Nothing is syntactically wrong. launchctl accepts it, and the job looks loaded. No signal anywhere says the config is wrong. It's just a different schedule than intended.
The next 1st hadn't arrived since loading, so from launchd's point of view, zero runs was perfectly correct.
What stale grounding costs
With monthly refreshes, posts go out grounded on store copy up to 30 days old. If I edit a store description in between, posts keep quoting the old text. Price is worse: the "free" check reads the same file, so a passing check is a pass against a stale value.
Having grounding at all creates false comfort. A source file exists, so you believe the posts are anchored to facts. But nothing was vouching for how fresh that source file was.
What would you do here: switch to daily, or rethink the schedule?
The fix
I removed Day = 1 and set the job to run every Monday. The commit title says it plainly: monthly on the 1st to weekly on Monday.
Then, instead of waiting for Monday, I forced one run to confirm the refresh: 47/47 listings, 47/47 ASO, exit 0. app_listings.json actually changed as a result.
What surfaced this job was the act of registering it on the monitoring board. So half the fix is one plist line, and the other half is bringing the job inside the monitored set.
Self-check
- For every file another pipeline reads, can you say right now when the job that produces it last ran?
- If a launchd
StartCalendarIntervalhas aDayorWeekdaykey, have you actually read it back as the schedule you meant? - Does your monitoring catch only failures, or does it also flag "never ran" and "no log file" as abnormal?
The honest part
Even weekly, the grounding can still be up to 7 days stale, and I have no evidence yet on whether that is enough. I also haven't checked whether any post published while the job was idle actually carried a description or price claim that differed from the store. I don't know exactly how old the pre-refresh values were either. The one thing I know is that without registering it on the board, I would have had no way to find out before the next 1st of the month.