ShipWall's morning pass reported success to the scheduler on every morning it had not run
We have a morning pass that dates the day's launches, freezes yesterday's ranking so it can never move again, and mails that ranking out. For a stretch of mornings it did none of those things, and the one thing watching it said it had finished fine, every day.
ShipWall is a launch board for products built with AI coding tools, the Lovable and v0 and Bolt and Cursor end of the world. The morning pass runs five steps in a fixed order: schedule, board, digest, badge, mailer. The order is load-bearing, because a digest sent before the freeze would quote a ranking that is still moving, and a maker's badge would then disagree with their own email.
launchd only keeps one number about a job
The scheduler running it is launchd, and what it records about a run is the exit code, the single number a program hands back when it stops. Zero means success. It has no opinion about whether the work happened, and it does not know what the work was.
It also hands a job less than you would assume. A launchd agent starts with a near empty PATH, so anything the script shells out to is not found, and it starts with no HOME, which is where every run script here resolves the shared job ledger from. With HOME unset, the ledger path does not exist, the reporting call is skipped without a word, and a job that died leaves no failure row at all.

The gate probed a name that was never a table
We added a preflight gate so a scheduled job would not fire into a stalled shared database and compete with its own recovery. The gate reads one row before the pass is allowed to start. The name we pointed it at was shipwall_board, which is the function that renders the ranking, and a function is not something you can select a row from. The database's REST layer answers that with 404, instantly, on a completely healthy instance.
The gate had one rule for anything that was not a 200: wait, then give up and exit 0, on the reasoning that a skipped night costs nothing. So every morning it waited, gave up, and reported success. Zero was the number in two places at once. It was the status handed back to launchd, and it was the number of mornings the pass had actually run since the gate landed.
| How the night ended | What launchd records | What the shared job ledger records |
|---|---|---|
| All five steps clean | 0 | ok, with the number of rows touched |
| One or more steps failed | 0, so launchd does not throttle the job over a slow badge crawl | fail, naming the steps |
| Database still refusing after 60, 180 and 420 seconds | 0 | fail, skipped after backoff, rows 0 |
| 401, 403 or 404 on the products table | 1 | fail, configuration fault rather than backpressure |
| No credentials in the environment file | 1 | fail, no credentials |
What changed
We now probe a real table, and we exit non-zero on 401, 403 and 404, because a bad key or a missing name will be just as wrong in seven minutes as it is now. Backing off 60, 180 and 420 seconds is reserved for the failures a wait can fix. Every terminal path in that script writes a ledger row carrying the number of rows the night touched, so a morning that dated nobody, froze nothing and sent nothing reads as a zero instead of as silence.
ShipWall is live if you want to see the board the freeze produces.
<caption>The job checked a name that did not exist, quit, and still reported success, so now it has to say what it did.</caption>
---
One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did, read off the repository and the live site, not written from memory. Join the list.