The Discord bot posted a clean daily summary card into #reviews at 3:40pm on a Wednesday, for a week that wasn’t over yet. That was the failure we set out to kill.
The integration was already built: daily, weekly and monthly review cards for our internal ops-tracking system, auto-posted on a schedule from the same session logs that drive the work log itself. On the first pass we backfilled the daily channel through Jul 22 and ran the weekly job across the last few weeks to seed history. That’s when it broke.
What shipped first
The weekly job for Jul 5 to Jul 11 ran fine. Then we reran it by accident a minute later, same range, and it posted again. The Jul 12 to Jul 18 card posted twice too. That made four weekly posts in ninety seconds, two of them exact duplicates, in a channel that was supposed to be a clean historical record.
The duplicates were a nuisance. The content of one of those cards is what made us stop. It was a “weekly review” for a week that included days with no session data yet. The cron had fired mid week, and the script couldn’t tell “no work happened” from “the data isn’t in yet.”
The wrong turn
Our first fix was rate limiting. We read the duplicates as a job-overlap problem, so we added a lock file, .state/weekly_post.lock, keyed on the date range. The poster bailed if a post for that range had gone out in the last hour. We shipped it and reran the backfill, and the duplicate weekly cards were gone.
The lock file never touched the problem underneath. Two days later the daily card posted for “today” at 8am, before most of the day’s sessions had started. It showed a mostly empty day that looked like a slow day and was really an early snapshot. The lock file had solved duplication and done nothing about completeness. We had fixed the symptom we noticed first instead of the mechanism under both symptoms, and we’d spent the better part of an hour building and testing a lock file that the eventual fix made irrelevant.
What actually needed to be true
A report card should never represent a day or week that isn’t done yet. “Done” here means that no new session data can still land in that bucket, and business hours ending has nothing to do with it. Two things can make a bucket incomplete:
- Still in progress on the wall clock: a Wednesday afternoon can’t summarize Wednesday.
- Past, but still mutable: a day can change after it ends if a session logged against it hasn’t closed. Our work log entries get written after the session finishes, and long sessions (the 17h 29m platform-monitoring one, the 13h 8m CPU-leak one) routinely span midnight.
So the gate is a precondition on the query itself. Before the daily job posts for date D, it checks that D < today in the reporting timezone. It also checks that no open session has a start timestamp before D+1 00:00 and no end timestamp yet. Before the weekly job posts for a range, every day in that range has to pass the same daily gate on its own. The monthly card requires every week in the month to have already posted its own weekly card, so completeness composes upward and nothing gets re-derived at each tier.
That’s why the backfill only went through Jul 22, the day before the run. It’s also why the weekly channel only got the two ranges that were fully closed, Jul 5 to Jul 11 and Jul 12 to Jul 18. The partial week the backfill ran inside of was rejected outright. The rejection came before any post attempt, so no duplicate or stale-looking card ever sat in the channel for someone to notice.
What stayed, and what carries the load
The lock file is still in there as cheap insurance against a cron double-fire, but nothing depends on it anymore. The completeness gate runs on every scheduled post, not just backfills. A cron that fires at a fixed wall-clock time has no idea whether the data behind that tick has settled.
Silence is a fine failure mode for a report. A confidently wrong summary sitting in a channel next to correct ones is worse, because nothing about it signals that it’s wrong. So before any automated report goes out, check that the period it covers is closed and that no open session can still add data to it.