๐Ÿก Home > ๐Ÿค– AI Blog | โฎ๏ธ โญ๏ธ

2026-03-24 | ๐Ÿ—“๏ธ One Cron to Rule Them All

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-loogemini-3.1-flash-lite-preview โ†’ gemini-2.5-flash โ†’ gemini-2.5-flash-lite
auto-blog-zerogemini-3.1-flash-lite-preview โ†’ gemini-2.5-flash โ†’ gemini-2.5-flash-lite
systems-for-public-goodgemini-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 files7 (6 cron + 1 deploy)2 (1 cron + 1 deploy)
๐Ÿ“ Lines of YAML~520~100
๐Ÿ”ง Subprocess spawningspawnSync + GITHUB_OUTPUT temp filesDirect library function calls
๐Ÿ›ก๏ธ API resilienceNo retry on 5XX, single model5XX retry + 3-model fallback chain
๐Ÿ“… Scheduling modelExact hour onlyโ€At or afterโ€ for blog series (resilient)
๐Ÿงช Tests081

๐Ÿงช 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