fix: offset midnight crons to avoid Cloudflare dedup collision#7
Conversation
Cloudflare Workers deduplicates overlapping cron invocations when multiple crons fire at the same minute. At 00:00 UTC, */5 * * * * collides with 0 0 * * * and 0 0 * * SUN — only one fires (the */5 regular update "wins", leaving daily rebalance and weekly reconstitution silently skipped). Moving the midnight crons to 00:01 UTC keeps them close to the intended time while eliminating the collision. Simplified the */5 handler since it no longer needs to skip midnight. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gp300 | c791fe6 | Commit Preview URL Branch Preview URL |
Apr 21 2026, 03:15 PM |
There was a problem hiding this comment.
Pull request overview
Adjusts Cloudflare Workers cron schedules and handler logic to prevent silent cron-trigger deduplication at 00:00 UTC, restoring reliable daily/weekly maintenance runs.
Changes:
- Offsets daily/weekly crons from 00:00 to 00:01 UTC and splits daily cron into MON–SAT vs SUN to avoid same-minute collisions.
- Simplifies the
scheduled()handler by removing midnight/sunday skip branches and routing based on the new cron strings.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
wrangler.toml |
Updates cron trigger schedules to avoid same-minute delivery collisions. |
src/worker.py |
Updates cron dispatch logic to match new schedules and removes prior skip logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if cron == "1 0 * * SUN": | ||
| # Weekly reconstitution (+ rebalance) — Sunday 00:01 UTC | ||
| await self._run_pipeline(reconstitute=True, rebalance=True) | ||
|
|
||
| elif cron == "0 0 * * *": | ||
| # Daily rebalance — skip on Sundays (reconstitution handles it) | ||
| if now.weekday() == 6: | ||
| return | ||
| elif cron == "1 0 * * MON-SAT": | ||
| # Daily rebalance — Mon-Sat only (Sunday handled by reconstitution cron) | ||
| await self._run_pipeline(reconstitute=False, rebalance=True) | ||
|
|
||
| else: | ||
| # Regular update (*/5) — skip the midnight tick (rebalance handles it) | ||
| if now.minute == 0 and now.hour == 0: | ||
| return | ||
| # Regular update (*/5) — always runs; offset midnight crons avoid collision | ||
| await self._run_pipeline(reconstitute=False, rebalance=False) |
There was a problem hiding this comment.
The else branch now treats any unrecognized event.cron value as the regular update. That can silently run the wrong pipeline if a cron string is changed/mistyped or another schedule is added later. Consider explicitly matching the */5 * * * * cron and, for unknown values, logging an error and returning (or raising) so unexpected cron values don’t trigger updates.
| @@ -488,20 +488,16 @@ async def scheduled(self, event, env, ctx): | |||
| cron = event.cron | |||
| now = datetime.now(timezone.utc) | |||
There was a problem hiding this comment.
now = datetime.now(timezone.utc) is no longer used in scheduled() after removing the midnight skip logic. Please remove the unused variable to avoid dead code and potential lint warnings.
| now = datetime.now(timezone.utc) |
- Explicitly match the */5 cron and log + skip any unrecognized cron string, so typos or new schedules don't silently run the regular-update pipeline. - Remove unused `now` variable from scheduled() after midnight-skip logic was deleted. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@copilot to review again. |
I re-checked the PR and the prior review points are already addressed in commit |
Summary
The midnight crons (
0 0 * * *and0 0 * * SUN) stopped firing after 2026-04-15. Root cause: Cloudflare Workers silently deduplicates cron triggers that fire at the exact same minute. At 00:00 UTC every day, the*/5 * * * *cron collides with the midnight crons, and only one invocation is delivered. The*/5regular update "won", leaving daily rebalance and weekly reconstitution silently skipped.Symptoms:
last_rebasestillnullon 2026-04-21 (should have been set by Sunday 2026-04-19 reconstitution)Fix
Move the midnight crons to 00:01 UTC and use mutually exclusive day-of-week ranges so none of the three schedules ever fire at the same minute:
*/5 * * * *1 0 * * MON-SAT1 0 * * SUNAlso simplified the scheduled handler — the
*/5handler no longer needs to skip midnight, and the daily cron no longer needs the Python-side Sunday check.Test plan
pytest test_engine.pypasses (11 tests)last_rebaseset)🤖 Generated with Claude Code