Automation Pipeline3 min read

The job that grounded my posts had never run

The launchd job that refreshes the store-listing file, the only grounding source for my auto-published Threads and Facebook posts, had zero runs since it was loaded. The cause was one plist key: Day=1. No errors, not even a log file, so nobody noticed.

#launchd#macos#scheduling#monitoring#grounding
A launchd job shown as loaded with zero runs, next to the Day key that makes it monthly
The config was valid; only the schedule was not what I meant.

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: 10

Nothing 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 StartCalendarInterval has a Day or Weekday key, 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.

Related