· Ben · craft · 6 min

You can already write blog posts with Claude Code. Here's what breaks when you do it every day.

The terminal demo works on run one. This is the list of things that quietly fail between run one and run thirty: cron attribution, tool grants, CLAUDE.md drift, topic memory, and figures nobody can source.

Here's the whole thing, about fifteen lines:

name: daily-post
on:
  schedule:
    - cron: "0 6 * * *"
jobs:
  write:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          prompt: "Write tomorrow's post. Match the voice in CLAUDE.md. Open a PR."

That works. I'm not going to pretend otherwise. Contentcron runs on the same model underneath. If you want to write blog posts with Claude Code, the writing is genuinely solved. Point it at your content/ directory, give it a decent CLAUDE.md, and run one comes back as a clean MDX file with frontmatter that matches your existing posts.

Search for this and you'll find run one everywhere. Skill suites with thirty-one sub-skills. A YouTube video that produces a full post in ten minutes. Hugo workflows, context-file guides, all of them excellent, all of them with a human sitting in the terminal and a unit of work of exactly one article. Separately there's a good body of writing on headless mode and GitHub Actions, aimed at CI chores (triage this issue, review this diff).

Nobody has written run thirty. That's the gap, and everything interesting lives in it. Here's what I hit.

The cron line has an owner, and it's a person

Two GitHub facts that only matter once the schedule is the point.

Scheduled workflows run from the default branch only. Fine, expected. The second one is sharper: GitHub attributes a scheduled run to the user who last modified the cron expression. Your Tuesday post is running as whoever nudged 0 6 * * * to 0 7 * * * in March. When that person offboards and you deactivate their account, the daily blog stops, and the reason is nowhere near the blog.

And on public repos, GitHub disables scheduled workflows after 60 days of repository inactivity. A quiet marketing-site repo where the only commits are the posts the workflow itself opens is exactly the kind of repo that trips this. The failure is silent. You find out because someone asks why the last post is from two months ago.

Writing blog posts with Claude Code is the easy half. The tool grants are the other half.

The action takes a plain-text prompt. What it doesn't come with is permission to do anything. No shell, no GitHub API, until you pass --allowedTools and enumerate what it's allowed to touch. That's the correct default and I'd build it the same way, but it means your fifteen-line workflow grows a list, and the list is where the daily version diverges from the demo.

A one-off post needs Write and maybe Bash(git:*). A daily post needs web search for research, file writes scoped to content/, a git surface wide enough to branch and commit but not wide enough to touch anything else, plus a PR-creation path. Then it needs to reject its own bot when the PR-review step fires, because the action refuses a bot actor unless it's listed in allowed_bots, and a pipeline that opens its own PRs will trip over that on day one.

One more thing while you're on that permissions screen. The Claude GitHub App requests read and write on Actions, Checks, Contents, Discussions, Issues and Members, because one app covers every feature it offers. That's a reasonable design for a general-purpose coding agent living in your repo. It's a lot of surface for a thing whose only job is to add a markdown file. Contentcron's GitHub App asks for contents and pull requests, and nothing else, because that's all a content pipeline can possibly need. Same model doing the writing. Different blast radius.

Voice lives in a file that gets read on every single run

CLAUDE.md is loaded into context every run. Anthropic's own guidance is to keep it concise for exactly that reason, and their published usage numbers are around $13 per developer per active day, landing most teams at $150 to $250 a month.

Now put your voice guide in there. Real voice context is not three adjectives. It's writing rules, banned constructions, five style excerpts long enough to actually demonstrate rhythm, a company-facts block so the model stops inventing your pricing. That's a few thousand tokens, and you pay for it 365 times a year whether the post needs it or not.

So you trim it. And the posts drift, slowly, in the direction of generic. Or you keep it fat and watch the line item grow. There's no clever resolution to that in a DIY setup, only a dial with a cost on one end and drift on the other. If you're on a subscription instead, the caps are shared with your Claude chat usage and they move. The 50% promotional increase ended on July 19. Your blog and your debugging session compete for the same weekly budget, and the budget is not a constant.

Nothing remembers what you already published

Run one has no history to contradict. Run thirty is being asked to write about your product for the thirtieth time, with a context window that starts empty.

You will get near-duplicates. Not identical posts, worse than that: two posts eight weeks apart that make the same argument with different examples, both ranking for nothing because they split the same intent. You'll get an article that flatly contradicts one from last month, because nothing read last month. And you'll get the safe topic on repeat, because absent a queue, the most obvious idea is always the most obvious idea.

No prompt fixes this. It's a state problem. You need a durable list of what's been covered, what's queued, what a human rejected and why, scored against live search results rather than against the model's memory of the internet.

Topic queue: Researched topic ideas split into queued, suggested and rejected, with controls to suggest topics, add your own, or write one now.

The rejected column is the part a stateless script can't have. It's the only record of your taste.

The revision loop resets every time

You can @claude a pull request and get a revision. That part works.

What doesn't carry is everything you said last week. You told it, on PR #14, to stop opening posts with a rhetorical question. PR #15 opens with a rhetorical question. You correct the same three tics every day, forever, and each correction costs a full context load.

Three comments on the same PR also become three independent revisions rather than one coherent pass, which is how you end up with a paragraph that's been rewritten twice in opposite directions inside one diff. Batching them, replying with a summary of what changed, folding the recurring corrections back into the brand profile so they stop recurring. Those are pipeline concerns, not model concerns. We wrote up the general shape of that in reviewing content the way you review code, and it's the part I'd rebuild first if I were doing this by hand.

Figures are where it stops being a tooling problem

Ask for a chart and you'll get plausible numbers. Not sourced numbers, plausible ones. Ask for a product screenshot and you get nothing at all, because the agent cannot log into your app.

This isn't a Claude Code limitation you can prompt around. Screenshots require credentials, a browser walking your nav, tagging by feature, and a re-shoot schedule with a pixel diff so the URL only changes when the UI actually changed. It's the same discipline that keeps documentation screenshots from quietly lying. Figures require a research pass that either returns a citable source or returns nothing.

Asset library: The per-project library of captured product screenshots and sourced figures, filterable by type and feature tag.

Our rule is that a figure which can't cite a source is rejected. Not flagged, rejected. There's no AI image generation anywhere in the product, by design, because every AI content tool I've tried illustrates posts with stock photos or generated art and you can smell it in half a second.

When you shouldn't build any of this

Twice a month, one author, one person who owns the blog. Use claude -p and a good CLAUDE.md. You'll do fine, and none of the seven things above will ever bite you, because none of them are run-one problems. Don't buy a pipeline for that.

The same goes if your blog isn't a folder of markdown. If the posts live in a database behind an API, there's no file to open a PR against and this entire class of workflow is closed to you, ours included.

The list above isn't an argument that the terminal can't write. It's the spec for everything around the writing: durable topic state, batched revisions that stick, a brand profile you don't pay to reload, an asset library with real credentials behind it, and a schedule that doesn't die because someone left the company. That's what I've been building, and the honest version of the pitch is that you could build it too. It just isn't fifteen lines of YAML.

If you'd rather see the output than the architecture, the first article is free and there's no card.