๐ก Home > ๐ค AI Blog | โฎ๏ธ โญ๏ธ
2026-03-24 | ๐๏ธ One Cron to Rule Them All

๐ฏ Six YAML workflow files, each with their own cron schedule, boilerplate setup steps, and duplicated secret mappings โ replaced by a single hourly cron and a TypeScript scheduler that calls library functions directly.
๐ค Why This Matters
๐ As the blog series grew from one to three, and social posting, internal linking, and image backfill joined the party, the .github/workflows/ directory accumulated six nearly-identical cron workflow files. Each one repeated the same checkout, node setup, cache, and obsidian-headless install steps โ differing only in their cron schedule and the script they called.
๐งน The proliferation of YAML meant that adding a new blog series required copying a 100+ line workflow file and tweaking a few values. Changing a shared pattern (like the node cache key) required editing every file. It was YAML programming, and nobody likes programming in YAML.
๐๏ธ The Architecture
๐ง The core insight: scheduling is data, not configuration. A pure TypeScript function maps UTC hours to task IDs. Blog series use โat or afterโ scheduling โ they become eligible at their hour and stay eligible for the rest of the day, with idempotency checks preventing duplicate generation.
| โฐ UTC Hour | ๐ท๏ธ Task |
|---|---|
| 15+ | ๐ Chickie Loo blog post (at or after, idempotent) |
| 16+ | ๐ค Auto Blog Zero blog post (at or after, idempotent) |
| 17+ | ๐๏ธ Systems for Public Good blog post (at or after, idempotent) |
| 6 | ๐ผ๏ธ Backfill missing blog images |
| 8 | ๐ Internal linking (BFS wikilinks) |
| 0,2,4,โฆ,22 | ๐ข Social media posting |
๐ง The orchestrator (scripts/run-scheduled.ts) calls library functions directly โ no subprocesses, no temp files, no GITHUB_OUTPUT parsing. Data flows through function returns.
๐งฉ Key Design Decisions
๐ Library Calls, Not Subprocesses
๐ง Instead of spawning scripts via spawnSync, the orchestrator imports and calls generateBlogPost(), processNote(), autoPost(), runLinking(), and other library functions directly. This eliminates the need for GITHUB_OUTPUT environment variable passing โ data flows through TypeScript function returns.
๐ โAt or Afterโ Scheduling for Resilience
๐ Blog series tasks become eligible at their scheduled hour and remain eligible for the rest of the day. Before generating, the orchestrator pulls vault posts and checks if todayโs post already exists. If the hour-15 run for chickie-loo fails, the hour-16 run will pick it up automatically.
๐ก๏ธ 5XX Retry with Model Fallback
๐ก All Gemini API calls now retry on transient errors (429, 500, 502, 503, 504) with exponential backoff. If a model fails definitively, the orchestrator tries the next model in a configurable chain:
| ๐ท๏ธ Series | ๐ค Model Chain |
|---|---|
| chickie-loo | gemini-3.1-flash-lite-preview โ gemini-2.5-flash โ gemini-2.5-flash-lite |
| auto-blog-zero | gemini-3.1-flash-lite-preview โ gemini-2.5-flash โ gemini-2.5-flash-lite |
| systems-for-public-good | gemini-2.5-flash โ gemini-2.5-flash-lite โ gemini-3.1-flash-lite-preview |
๐ Grounding Fallback
๐ก For grounding-enabled requests, if grounding fails with a quota error, the request is retried without grounding on the same model before trying the next model in the chain.
๐ Before and After
| ๐ Metric | โ Before | โ After |
|---|---|---|
| ๐๏ธ Workflow files | 7 (6 cron + 1 deploy) | 2 (1 cron + 1 deploy) |
| ๐ Lines of YAML | ~520 | ~100 |
| ๐ง Subprocess spawning | spawnSync + GITHUB_OUTPUT temp files | Direct library function calls |
| ๐ก๏ธ API resilience | No retry on 5XX, single model | 5XX retry + 3-model fallback chain |
| ๐ Scheduling model | Exact hour only | โAt or afterโ for blog series (resilient) |
| ๐งช Tests | 0 | 81 |
๐งช Testing
๐ฌ 81 tests verify scheduler logic, CLI parsing, error classification, and slug generation. Every hour maps to the correct tasks, model chains are complete, and the idempotency check works correctly.
๐ Lessons Learned
๐ YAML is for declaration, not computation. When you find yourself copying and tweaking YAML files to handle scheduling variants, the scheduling logic belongs in code โ where it can be typed, tested, and composed.
๐ Library calls beat subprocesses. Passing data through function returns is simpler, type-safe, and eliminates the fragile GITHUB_OUTPUT temp-file dance.
๐ก๏ธ Resilience compounds. โAt or afterโ scheduling + existence checks + 5XX retry + model fallback means blog generation is robust against transient infrastructure failures.
๐ Book Recommendations
๐ Similar
- ๐ A Philosophy of Software Design by John Ousterhout โ reducing complexity by consolidating related logic into cohesive modules
- ๐ Release It! by Michael Nygaard โ production-ready patterns including scheduling, isolation, retry, and failure handling
๐ Contrasting
- ๐ Infrastructure as Code by Kief Morris โ argues for declarative infrastructure definitions, which this refactoring pushes back against for scheduling concerns
๐ Creatively Related
- ๐ ๐๐๐ง ๐ Thinking in Systems: A Primer by Donella Meadows โ the hourly scheduler is a feedback loop: time triggers evaluation, evaluation triggers action, idempotency checks close the loop