DEV Community

Jeremy Longshore
Jeremy Longshore

Posted on • Originally published at startaitools.com

Producer Fallback: When Claude Hits the Weekly Limit, the Pipeline Still Ships

A daily content pipeline that dies because one model returns a rate-limit error is measuring the wrong success criterion. The job is not "Claude ran." The job is "yesterday has a post on the live site."

On 2026-07-15 that distinction stopped being theoretical.

What broke at 04:00

Two failures, one after the other:

  1. Dirty tree preflight. Closing a bead the night before left one uncommitted line in .beads/interactions.jsonl. preflight_branch_normalize correctly refused to run on a dirty master. No produce step. No post for 2026-07-14.
  2. Claude weekly limit. After the tree was cleaned and the run re-fired, claude -p /blog-backfill exited in three seconds with: weekly limit, resets Jul 19. Land saw no post file and reported NO-POST (rc=20).

That is the same shape as automation assurance on Intent-OS: exit codes and "the scheduled job ran" are not outcome verification. The outcome is a live URL.

Fix one: do not brick the cron on beads interaction noise

.beads/interactions.jsonl is an append-only session audit log. Any bd close dirties it without committing. Treating that as "human has uncommitted feature work" is wrong; treating real content dirt the same as always is right.

preflight_branch_normalize now:

  • Still FATAL on any tracked dirt that is not interactions-only.
  • If the only dirty path is .beads/interactions.jsonl, auto-commit it with a mechanical message and continue.

That keeps the clean-tree invariant for posts and methodology files without letting a late-night bead close silence the morning publish.

Fix two: hard voice lint (already on master)

Separately, the produce path now fails closed on AI voice fingerprints: em dash / en dash hard ban, expanded slop phrase list, deterministic lint-post-voice.py in the skill and again in blog-land.sh. Phrase checks mask fenced code and URLs so repo names do not false-positive. Historical posts are not bulk-rewritten; only the post being landed is gated.

That gate is what made the recovered 2026-07-14 post ship without the em-dash density that had become the house default.

Fix three: Grok as producer fallback

Claude remains the primary producer. It is not the only producer.

blog-backfill-daily.sh now:

  1. Tries claude -p /blog-backfill (same as before).
  2. If Claude fails and no post exists for the target date, runs a Grok headless producer with the same contract: write the post, append decisions.jsonl, write .blog-staging/DATE.intent.json with ready:true only after gates (including voice lint). No git.
  3. Always runs blog-land.sh afterward (verify → commit → push → dual-publish → queue, or quarantine).

Env knobs:

Variable Values Meaning
BLOG_PRODUCER auto (default), claude, grok Which producer(s) to run
GROK_BIN path Defaults to ~/.grok/bin/grok
BLOG_GROK_MAX_TURNS int Headless turn cap (default 120)

auto is the production setting until Claude is healthy again, and it stays useful after: a weekly limit or API blip should not equal a missed day.

What actually shipped for 2026-07-14

While Claude was dark, the 2026-07-14 post was produced manually under the same land contract:

The content thesis of that post and the ops lesson of this one are the same sentence: exit 0 is not success; verify the outcome.

Also on the board today

  • Release bumps and README/changelog sync after the voice-lint and preflight commits landed on master.
  • IAM Bob packaging release noise (v2.1.6) from the overnight auto-release path: docs-only, not the story of the day.

Takeaway

Producer outages will happen. Rate limits, model downtime, auth glitches. The pipeline should treat them like any other dependency failure:

  1. Do not fail open (publish half-baked).
  2. Do not fail silent (alert + log).
  3. Do not single-home the produce step on one vendor when a second local producer can honor the same artifact contract.
  4. Keep land deterministic and dumb: it does not care which model wrote the markdown.

Tomorrow's 04:00 run can try Claude, fall back to Grok, and still land. That is the bar.

Related Posts

Top comments (1)

Collapse
 
skillselion profile image
Skillselion

Producers-as-swappable-artifacts is the right frame: land does not care which model wrote the markdown. As the Grok path gets real traffic, the thing I'd watch first is the lint. Your voice lint was tuned on Claude's failure fingerprints - the em dash density, that phrase list. Grok's slop profile is different, so a gate that fails closed for Claude may quietly fail open for Grok until the ban list has seen enough Grok output. I'd hand-diff the first dozen Grok posts against the lint rules.

The second risk is quieter: the two producers share every input - same beads, same decisions.jsonl, same repo state. A poisoned or malformed context file takes out both on the same morning, and the fallback buys you nothing. The dirty-tree preflight you just fixed is exactly that class - worth asking which other shared inputs can fail both paths at once.