A background job that is supposed to text you can hang forever on a permission prompt it cannot display. No error. No message. And the monitor keeps reporting itself healthy.
Under launchd, a job that drives Messages through osascript blocks on a
TCC Automation prompt. A background context has no way to show that prompt, so nothing resolves
it. Measured on a real system: a 90 second timeout, no error returned, and no message sent.
The failure is not the hang. It is that silence looks identical to nothing happened. A watchdog wired to text you would go completely quiet overnight while its own health check stayed green — the exact failure such a system exists to catch, sitting inside its own notification path.
Jobs run from cron have no keychain access, so anything needing a stored credential
fails with errors like could not read Username: Device not configured. And on recent
macOS, scheduled jobs are blocked from reading ~/Documents entirely, which produces a
partial view of the filesystem rather than a clean permission error.
A partial view is worse than no view. Code that sees half a directory usually concludes the missing half does not exist, and reports that conclusion confidently.
Exit code zero is not delivery. To know a message actually arrived, check the store that records it rather than the tool that sent it:
# osascript exiting 0 proves nothing was raised — not that anything was delivered.
# Messages writes the row, so poll for the row.
sqlite3 ~/Library/Messages/chat.db \
"select count(*) from message where is_from_me=1 and date > ?;"
Three rules that come out of this, in order of how much they save:
Because it happened to us. These are notes from running an estate of machines that watch each other, where a silent notification path is the one failure that cannot be tolerated.
Work gets done without you thinking about it. A RapterBox runs this kind of job overnight, refuses to claim success it has not verified, and texts you one brief in the morning. Join the waitlist →