14 days of daily publishing: the three failure modes I hadn't designed for

The slug was mortgage-abandoned-about-above. Day 2 of a 14-day publish cadence, the morning cron fired at 07:00 UTC and produced one draft. That’s what the title-mangling step left in the filename. Not a build failure. The draft existed on disk — 10 of 13 sentences passing the quality gate — grounded in BBC, Zoopla, Moneyfacts, and Bank of England sources. The pipeline considered its work complete. The slug was just wrong.

That was the first of three failure modes the cadence surfaced. None of them were the failures I would have predicted before I started.

The setup

UK Calculators runs a morning cron that triages news clusters, fetches the top candidates, and produces drafts for operator review. The intent is a daily article: timely news pieces when the cron produces well, evergreen pillars when it doesn’t. A two-lane system.

I set a 14-day target because a week isn’t long enough to distinguish a good system from a lucky one. 14 days covers weekdays, weekends, Monday morning news bursts, and quiet Fridays. It also covers the compounding effects of decisions made on Day 2 that only surface as problems on Day 8.

Each failure mode below happened on a specific day at a specific pipeline stage.

Failure mode 1: Title-mangling writes permanent ugly slugs

Day 2, 30 June. The draft was substantive: grounded in BBC, Zoopla, Moneyfacts, and BoE sources, with £232 and £66 regional monthly payment figures, 10 of 13 sentences passing the quality gate. The title-mangling step was mid-fix. The slug it picked was mortgage-abandoned-about-above.

The problem isn’t aesthetics. It’s canonicalisation. A slug is the article’s permanent URL. A URL like /mortgage-abandoned-about-above gets indexed, linked, and cached before anyone notices it’s wrong. Fixing it later means a redirect. Redirects compound. Enough of them and the crawl budget starts fragmenting.

The fix needs to happen before the slug is written to disk, not at review time. The operator shouldn’t be making slug-correction calls on every publish; that’s a pipeline responsibility, and it should fire at generation, not at approval.

Out of scope: the specific fix. That’s a separate sprint. The point is that this surfaced on Day 2 and would have been invisible in a one-off publish. Daily frequency made it impossible to defer.

Failure mode 2: Zero-draft mornings need a fallback

Day 4, 2 July. Cron fired at 08:22 BST. Triage completed — 150 clusters ranked, mortgage sitting top by calculator score at position 20. Candidates fetched. Zero drafts produced.

First zero-draft firing of the streak. The cron was healthy. Triage ran. The candidates existed. The ground-truth check determined none met threshold for a news draft. From the pipeline’s perspective, correct behaviour. From the publish calendar’s perspective, a gap.

A daily cadence with no fallback produces silence. The history shows Day 1, Day 2, Day 3, then nothing. Readers don’t see pipeline health metrics; they see the gap.

The answer is a pillar queue: evergreen articles that don’t depend on the news cycle, drafted ahead of time and held in reserve for exactly this case. Days 3, 5, and 6 demonstrated the pattern working in practice — Personal Savings Allowance, ISA allowance and student loan repayment thresholds, UK House Price Index April 2026 and Pension Annual Allowance. Most were operator-published rather than cron-produced, but they filled the calendar and the cadence held.

The Day 6 devlog entry explicitly labels the UK House Price Index April 2026 piece as “Lane B’s first data-pegged article” — meaning the two-lane distinction (Lane A: news, Lane B: gov.uk data-pegged pillars) had been formalised by that point, even if the switching logic between them isn’t automated yet.

Out of scope: automating pillar selection and the zero-draft fallback trigger. Today the pillar queue is manual. The first step is having the queue; automation is a later sprint.

Failure mode 3: Triage without memory republishes the same cluster

Day 3, 1 July. Cron fired at 08:21 BST. Produced one draft — on the same mortgage rate-spike cluster published the previous day. Same BBC, Zoopla, Moneyfacts, and BoE sources. Same US-Iran framing. Same £232 and £66 regional figures.

Day 2 had shipped a mortgage rate-spike article. The Day 3 cron had no record of that. Triage ranked the cluster highly because the underlying signals were still strong: the cluster hadn’t gone cold, the calculator score was still elevated. The system produced the right answer to the wrong question. It found the best available cluster; it just wasn’t tracking which clusters it had already used.

Republishing the same cluster with the same sources two days running isn’t a content strategy. It’s a bug dressed as high engagement.

The fix is a published-cluster exclusion list. Before triage selects candidates, it checks recent publishes and drops any cluster whose core sources already appeared in an article within an exclusion window. The window length needs careful tuning: long enough to prevent next-day duplication, short enough not to permanently suppress a story that genuinely develops — a mortgage rate that moves again in three weeks is a different article.

This is the failure mode I would have predicted least. Before the cadence started, the worry was volume: would the cron produce enough content? The actual problem was similarity: it produced content too close to what it had already shipped, with no awareness of its own history.

What 14 days exposes that a week doesn’t

A single good week is consistent with a lucky cluster seed. 14 days compresses a wider range of conditions and — critically — includes the second pass through topics that dominated the first week.

Each of the three failures has this property: it needs sustained frequency to become visible.

Title-mangling was mid-fix on Day 2. Without cron output on Day 2, it stays a development concern, not a production one. The slug sits in a draft that nobody publishes, and the problem defers indefinitely.

Zero-draft mornings need to happen before the fallback queue becomes non-optional. Day 4 made it concrete. Before Day 4, a pillar queue was a reasonable idea. After Day 4, it was a gap that needed closing.

Stale cluster republishing only matters at daily cadence. One article per week and the Day 3 cluster is stale by definition — there’s no pressure to re-select it. Daily, and the same high-ranking cluster reappears the next morning with a better score than anything else in triage, because the underlying news hasn’t moved on.

The cadence is a stress test. It found three failures. That’s the expected output of running a system hard for two weeks.

What’s next

Title-mangling needs a fix before the next cron cycle produces another permanent ugly slug. The pillar queue is operational but entirely manual; automating the zero-draft fallback is the next structural step. The stale-cluster exclusion needs a window-length decision and an implementation sprint.

Day 6 also surfaced a fourth issue: an AppleEvent -1712 resource lock-up delayed the cron until Cj manually intervened and re-kicked at 08:35. System resource contention is a different category — infrastructure rather than pipeline logic — and it’s deferred.

Three failure modes found, three fixes queued. The per-day entries are at docs/devlog.md. When the fixes ship, the next article will cover which of the three was harder to close than the diagnosis suggested.

All writing