How my Saturday blog posts get written before I wake up

·4 min read·claude, automation, content, routines
ShareXLinkedIn

It's Saturday, 06:00. I'm asleep.

A Claude Code routine fires on my Mac, reads the last week of project handoffs from my Obsidian vault, drafts a blog post about whatever I actually built that week, and opens a pull request on GitHub. By the time I open the laptop with coffee, there's a PR titled Blog draft: <slug> waiting for me to edit and merge.

The whole thing took 12 sessions to build. Here's how it works.

The handoff problem

Every time I finish a coding session, I write a handoff. It lives in bambo/10_projects/<project>/handoffs/YY-MM-DD-HHMMSS-<topic>.md. Frontmatter, what was done, what's next, file paths, the failure of the day. It's not for blog posts. It's for the next me, two days later, who forgot what state things are in.

Turns out handoffs are a near-perfect blog post source. They're written in plain language, full of specifics (file paths, decisions, broken assumptions), and they cover the boring 80% that nobody documents. The technical stuff that an investor or a peer would actually care about is already in there.

The engine is mostly plumbing to take that material and turn it into something readable.

The routine

The Claude Code Routine is configured to run Saturday 06:00 and produces one PR. Its prompt reads, in essence: "Pick a thread from this week's handoffs. Frame it as a problem, what I tried, what failed, what worked, the lesson. Don't open with 'I built'. Don't say 'shipping'. Write it like you'd explain it to a friend over coffee."

The hard rules are short on purpose. Anti-bragging is two lines: never start a sentence with "I built/shipped/launched", and never put "crushing it / shipped / building / journey" in a headline. The rest is enforced by the voice references: Pocock, Vassallo, Marc Lou. The model knows what they read like.

The launchd

There's a launchd plist that runs at 05:00 every day. Its job is small and ungrateful. It rsyncs the latest handoffs from the Obsidian vault into the engine's repo, runs git pull, refreshes the project state files, and exits. It's a 14-line shell script. It exists because I don't want the routine to spend time fetching state when it should be spending time writing.

When the routine fires at 06:00, the data is already there.

What it reads

The engine pulls from six sources before drafting:

  1. All handoffs from the last 7 days, one per project
  2. Git commit logs from the last 7 days
  3. Obsidian Now, Tasks, and recent session notes
  4. Claude Code memory: project_state and project_lessons for each active project
  5. The latest /last30days output if one exists
  6. Anything I changed in CLAUDE.md or the skill files that week

If the week is light (no fresh handoffs, no commits), it switches to lateral mode: it reaches back 30 days and writes about something process-related. The CLAUDE.md changes alone are usually a story.

What it ships

The output is a markdown file at content/blog/YYYY-MM-DD-<slug>.mdx, plus a visual brief for a cover image, plus a PR titled Blog draft: <slug>. The PR has the front matter pre-filled, tags inferred from the source handoffs, and a draft flag set to true so it doesn't deploy until I flip it.

I read the PR on my phone over breakfast, edit in GitHub's web editor, flip the draft flag, merge. The post is live before I sit down to work.

What I screwed up

The first version generated a generic stock-photo cover for every post. They all looked like AI-generated stock-photo covers. I had a Saturday where four PRs in a row showed up with the same beige abstract gradient as the visual.

The fix was a dual-prompt template lifted from the klapa.hr blog system: the routine generates both a "cover" prompt (1.91:1, social-card friendly) and an "inline" prompt (square, content-relevant), and writes both into the visual brief as a markdown snippet I can drop into Midjourney. The covers stopped looking the same the next week.

Second screwup: PR backlog. If I went two weeks without merging anything, the routine kept running and stacking unmerged PRs. The fourth one had the same angle as the first because the source material hadn't moved. I added a 30-day rule: if the oldest unmerged PR is older than 30 days, the routine sends a notification and pauses itself. Resume is manual.

What it costs

Server: my Mac. Cost: 0.

Claude: included in Claude Max, flat rate. Cost: 0 incremental.

Time, including the launchd plist and tuning prompts: about 12 sessions, mostly on Saturday mornings. Most of that was the visual brief part.

Time to write a post once the engine is wired up: 5 to 15 minutes for me on PR review and edits.

What it's not

It's not autoposting. The PR has to be merged manually. The post has to be cross-posted to LinkedIn or X manually. I want a human in the loop because the worst version of this is a blog full of AI-generated text shipped while I'm not looking.

It's not perfect. About one in three drafts is unusable - wrong angle, awkward opening, fact that's slightly off. I rewrite or kill those. The other two thirds are 80% of the way there, which is more than I'd produce on my own at 6am on a Saturday.

It's not exclusive to me. The runner is Claude Code. The routine is a configuration file. The launchd plist is fourteen lines. If you have Claude Code and a habit of writing project notes, you can build the same thing in a weekend.

Why bother

The honest answer is that I have four projects, a full-time job starting in May, and a kid arriving in July. Writing posts manually was already losing to my schedule. Now the schedule loses to the routine.

Currently building: Klapa.hr and LexBox. Interested? Get in touch

M

Max Mucko

Entrepreneur and builder based in Croatia. Writing about health, tech, and building in public.