To publish blog posts consistently every week, I spent one day building a system that hands topic selection, article generation, image integration, and automated posting over to Claude Code. How do you surface "recent trends the LLM doesn't know about"? How do you design "two Claude Code sessions coordinating through git"? How do you keep "a Friday-night cron job" stable? This post covers all three rounds of trial and error and where each one landed. With Claude Code, a system this size gets up and running in a single day.
What you'll get from this post
- An approach to automating blog topic selection with Claude Code
- How to offload heavy work like image generation to a separate Claude Code session via git
- The pitfalls of running headless under cron, and how to work around them
- A way to think about where "let Claude Code handle it" ends and "a human decides" begins
What I used
- Claude Code — Anthropic's CLI agent. The star of this post
- Blogger (publishing target) + Blogger API v3
- GitHub Pages — image hosting
- Python — the topic-scouting script and the MCP server
- WSL2 + systemd cron — fires every Friday at 18:00
First step: feed existing posts to Claude Code so it learns the voice
I've been writing this blog since 2015, and there are roughly 80 posts. When you have AI write a new one, anything that doesn't match the established voice, structure, and category mix reads as "someone else wrote this." That's jarring for regular readers and for people arriving from search alike.
So the first thing I did was have Claude Code read every existing post and summarize the style guide and topic tendencies. Concretely:
- Back up the HTML of every published post locally via the Blogger API (
posts.list+posts.get) - Ask Claude Code: "so that you can write as this same author going forward, analyze the stylistic traits, the section structures used most often, the category distribution, and the phrasing to avoid — and write it up so it can be reused"
- Save the output to
memory(Claude Code's mechanism for persistently holding project-specific knowledge)
Examples of what it extracted:
- Phrasing habits: practical-article vocabulary like "the shortest path," "I'll write all of it," "where you'll get stuck," and the right dose of the author's humour (keep it to one or two sentences)
- The section structure used most: lead → what I used → steps → where you'll get stuck → wrap-up → related posts
- The distribution across 14 categories: electronics / Raspberry Pi / Arduino / IoT / AI / 3D printing / product reviews / Roomba / gadget modding / speech synthesis, and so on
- Phrasing to avoid: excessive emoji, a tone so serious it narrows the audience, and repeated hedging
- The full list of post titles from the past 8 years: the source for duplicate-checking so the same topic isn't covered twice
With all of that in memory, Claude Code can write new posts as a continuation of its past self. Continuity between newly generated posts and older ones holds up. Because memory persists across sessions, there's no need to paste a long style guide into the prompt every time.
Why automate it
I want to publish consistently every week. At the same time, I don't have the hours to research "what's been going on lately" from scratch each round.
If you simply ask Claude Code to "write about a trending electronics topic," it pulls from topics it already knew at training time. That drifts slightly away from the last year's trends and from what I'm actually interested in.
The fix was to feed in "recent trends," "my own interests," and "avoid overlap with past posts" as structured information from outside. Give up on having the LLM do it all, do the legwork ahead of time in a Python script, and hand the result to Claude Code as input.
The overall flow
topic scouting → article generation → image integration → draft posting
topic scoring
image generation service
image hosting
Sticking point 1: topic selection — surfacing trends the LLM doesn't know
This is where I struggled most. Even with the voice pinned down by memory, deciding what to write about is a separate problem.
First attempt: WebSearch alone → the keywords skew
I started by asking Claude Code to "write about a trending electronics topic" and letting it run WebSearch a few times. In practice the keywords skew. They converge on the standbys the LLM knows well — "Raspberry Pi," "ESP32," "Home Assistant." It couldn't focus on recent releases (Pico 2 W, XIAO ESP32-S3, SCD41 and the like), and it kept circling back to material I'd already covered.
The cause: the WebSearch queries themselves depend on words inside the LLM's knowledge space. It doesn't know the new words, so they never make it into the query, so the results never surface the new trends — a chicken-and-egg problem.
The fix: feed in structured "trends from the past year" from outside
I wrote research_topics.py, which periodically pulls 20 RSS feeds — 10 Japanese, 10 international — and extracts and scores keywords from the past year of article titles. The breakdown:
- 10 Japanese: Qiita, Zenn, MONOist, ASCII.jp, Gizmodo Japan, Kaden Watch, ITmedia, IoT News, deviceplus, ROBOT WATCH, and others
- 10 international: Hackaday, Adafruit Blog, Hackster, CNX Software, Tom's Hardware, The Verge, Engadget, Home Assistant Blog, Ars Technica, and the official Raspberry Pi blog
Japanese sources alone skew toward domestic material, so running the 10 international feeds alongside them surfaces stories that broke overseas but haven't been covered in Japanese yet. Topics that are big abroad but absent from Japanese sites become candidates in the "first Japanese-language write-up" slot.
The scoring formula
Each keyword gets a score:
score = frequency × (1 + cross_source × 0.4) × cross_region_bonus × affiliate_fit × novelty × sns_boost
- frequency: how many articles the keyword appeared in over the past year
- cross_source: how many sites mention it (broad coverage scores higher)
- cross_region: a bonus if it appears in both Japanese and international sources (a more reliable trend)
- affiliate_fit: whether the topic maps to a physical product you can buy on Amazon (microcontrollers, sensors, and parts score high; pure software scores low)
- novelty: cross-checked against the list of past post titles (fetched via the Blogger API) — topics already covered get pushed down
- sns_boost: see below
The SNS signal: a 2× boost for gadgets I've actually touched
What the world is talking about and what I've personally mentioned on social media are two different things. The former alone doesn't reflect my own interests, and writing about hardware I've never tried produces thin material.
So I download the official X and Facebook archives (the ZIP you get from a personal data export), unpack them under sns_data/, and extract keywords from a year of my own posts. Keywords that appear in the external RSS feeds and that I've also touched on social media get a 2× boost.
What rises to the top, then, is the intersection: topics the world is excited about that I've also had my hands on. Those I can write about from experience.
I refresh the SNS archives manually once a month or so. Using the official archive instead of scraping an API means nothing breaks when X or Facebook change their API terms, and the images come bundled too, so they double as photo material for posts.
The blocklist: separating "gadget hacking" from "life hacks"
Another snag: generic words like "life hack" kept climbing into the top results — app combinations, time-saving tricks, and other general-interest material this blog's readers aren't here for.
I filtered those out with an explicit blocklist. I needed a string-based rule that distinguishes "electronics modding" from "a handy life tip." It's obvious to a human, but the LLM's judgement wobbles, so pinning the rule down as literal strings is more stable.
22 article types, rotated
One more layer: a taxonomy of 22 "article types", so I don't ship microcontroller-plus-sensor builds week after week. A few of them:
| Type | Description |
|---|---|
| A | Microcontroller + sensor (Pico/ESP32 + I2C temperature/humidity/CO2 → MQTT, etc.) |
| B | Modding off-the-shelf gadgets / API hacking (SwitchBot, Echo, AirTag, etc.) |
| C | 3D printing + building mechanisms |
| D | AI at the edge (Ollama on a Pi, local speech and image recognition) |
| M | Retro and emulation |
| N | Clocks and timekeepers (word clocks, Nixie clocks, E-ink calendars) |
| P | OS migration, dual boot, turning machines into servers (Asahi Linux, Proxmox, etc.) |
| T | Dev environments, IDEs, workflows (Node-RED, WSL2, Claude Code — this post's type) |
| … | 14 more (robotics, wireless and comms, power, audio, storage, reviews, wearables, custom PCs, and so on) |
A classifier maps keywords to types with regular expressions, and which types dominate the last 5 posts is included in the research_topics.py output. Per instructions in CLAUDE.md, Claude Code follows the rule "if 2 or more of the last 5 posts share a type, pick a different one." That's what keeps two microcontroller-plus-sensor posts from landing back to back.
What the output looks like
The script produces a candidate list like this each run (simplified):
# Type distribution across the last 5 posts: A×2, B×3 → prefer anything but A and B
# Top 8 candidate topics
1. ✨🌐💰 Pipe CO2 readings from a XIAO ESP32-S3 into Home Assistant via ESPHome [type A]
Basis: mentioned by 4 JP × 6 international sites / touched on 3 times on social / high affiliate fit
2. 🌐💰 Turn a Famicom clone into a Wipeout port machine [type M, retro]
Basis: mentioned by 5 international sites (no JP coverage) / medium affiliate fit
3. 💰 Turn a Mac mini into a Linux server with Asahi Linux [type P, OS migration]
...
(truncated)
Each candidate carries ✨ SNS signal / 🌐 cross-region / 💰 affiliate fit flags, along with the supporting evidence and the matching article type. Claude Code picks one to three of them that don't collide with the recent type distribution and moves on to the deep dive (the hacking-techniques research in Step 1-C).
Splitting the work this way — structured information fed in from outside, the LLM doing the choosing — got topic selection working reasonably well.
Sticking point 2: where do the images come from
The cover image (the OG image) is what shows up as the thumbnail when a post is shared, so going without one costs you a lot of click-through. An LLM can't generate images on its own, so this needs an external service.
I tried the options: uploading manually through the Blogger admin (a bad fit for API automation), the xAI Grok API (monthly subscription), and running SDXL on a local GPU (what I went with). I landed on standing up ComfyUI on a PC with a GTX 1660 SUPER, generating locally, and serving the results through GitHub Pages.
The build-out of that local GPU image server itself — plus pushing generations toward how the real hardware looks, or converting them into watercolor illustrations — is covered in detail in the previous post on generating blog images on a local GPU. This post picks up from there and focuses on how I used Claude Code once I wanted to evolve that image service.
Sticking point 3: getting two Claude Code sessions to coordinate through git
This was the most interesting part. The side that writes posts (BlogGen) and the side that generates images (ImgGen) live in separate repos, on separate machines, with separate responsibilities. Each has its own Claude Code session.
But they still need to move together. When I wanted to add a --style illustration option on the ImgGen side, for instance, the calling side in BlogGen needed to know that spec too.
The approach I took: make a spec file in git the contract both sides read.
article generation, caller scripts, hosts the spec
separate machine, the image service itself, the implementation
docs/<feature>_spec.md)the contract both sides read
The cycle in practice:
- Stage 1 (spec filed): the BlogGen session writes the new feature's argument names, expected behavior, and workflow JSON spec into the spec file and pushes
- Stage 2 (implementation): the ImgGen session (on the GPU box) pulls, implements, and pushes back
- Stage 3 (verification): BlogGen pulls, runs real tests, and checks the quality of the generated images
- Stage 4 (feedback): BlogGen appends the measured values (recommended parameters) to the spec and flips the status to ✅
What worked well:
- Nothing stalls: while the ImgGen side implements, BlogGen can move on to other verification or article generation
- Less API drift: with the spec acting as a contract, "the argument name is subtly different" mostly stops happening
- Each session only has to think about its own repo: lower cognitive load
- The record lives in git history: which phase ran, when, and from which session is all traceable
Where it bit me:
- An overwrite from concurrent edits: once, the ImgGen side had pushed a separate fix just before BlogGen pushed the spec. I didn't notice, PUT with a stale SHA, and nearly overwrote it — recovered with the commit right after
- The lesson: when updating the spec with the gh CLI, always re-fetch the latest SHA with
gh api repos/.../contents/foo --jq .shaimmediately before the PUT - When renaming an argument, always leave a backward-compatible alias (otherwise the caller breaks)
Sticking point 4: the Friday cron and the permission allowlist
Every Friday at 18:00 JST, cron runs Claude Code headless with --permission-mode acceptEdits. It executes the given prompt once and exits.
First attempt: a silent timeout on the cron run. The cause was permissions. In an interactive session Claude Code asks "may I run this Bash command?" each time; headless, there's no one to answer, so it stalls on anything not already allowed.
The fix: pre-register the necessary Bash patterns and WebFetch domains under permissions.allow in .claude/settings.local.json. An allowlist approach, adding the operations you need one at a time.
An operational note: every time a new tool call becomes necessary, the allowlist needs another entry. I skim the cron logs (logs/weekly_draft_*.log) weekly, find the operations that stalled for lack of permission, and add them to settings.local. "Run it, break it, fix it" turned out to be faster than "read everything up front and add it all without gaps."
One example: a compound scp pattern like Bash(scp take@host:* *) is invalid when :* sits in the middle of the pattern. You have to rewrite it as Bash(scp take@host:**), with :* at the end. Local snags like that surface too — Claude Code's /doctor command warns about them, so you can catch them.
Sticking point 5: what to hand to Claude Code vs. what a human decides
"Let the AI do everything" is scary. "Have a human do everything" puts you back at the original problem (no time). You need a line.
The design I settled on:
| Phase | Owner |
|---|---|
| Trend extraction from RSS / SNS | script |
| Picking one topic from the candidates | Claude Code |
| Generating the article HTML | Claude Code |
| Checking pharmaceutical and advertising-law wording | Claude Code |
| Searching for and fetching reference images | Claude Code |
| AI image generation | separate session |
| Pushing images to GitHub Pages | Claude Code |
| Posting the draft via the Blogger API | Claude Code |
| The final publish button | human |
| Checking facts and the title | human |
The key is stopping at the draft. It posts a draft through the Blogger API, but it doesn't press publish. A human reads it and does the final pass on voice, facts, and title before it goes out.
That makes a bad post reversible, which buys psychological safety while Claude Code is experimenting. It mattered in practice: during the stretch when the early topic-selection script was churning out "life hacks," and while I was tuning image generation parameters, stopping at the draft is what let me try things without worrying.
FAQ
Q. What happens if draft generation fails?
A. Cron starts weekly_draft.sh and Claude Code runs; if it stalls partway or lacks a permission, an error is left in the log and no draft appears. That's all. It just runs again the following Friday, so nothing catastrophic happens.
Q. Why split Claude Code into two sessions? Can't one do both?
A. They're physically separate machines (one is the PC with the GPU, the other my everyday Linux box), so the Claude Code session pinned to each environment can reach different things. Running them separately means each only has to think about its own repo, which lowers cognitive load. Making the spec in git the contract is what makes this style work.
Q. Won't you go fully automatic and publish without review?
A. No. Keeping "a human looks at it last" as a premise is what makes it easy to experiment with AI generation. Misinformation and regulatory wording can't be fully checked automatically, so the last gate stays with a human.
Q. How often does the topic-selection script run?
A. On each cron run, so once a week. It computes the past year's keyword analysis from RSS and the SNS archives on the spot, so the results change every week. The SNS archives get re-downloaded and overwritten manually every month or so.
Q. How long did it take to build?
A. One full day. The RSS scouting script, the MCP server, the Blogger API integration, the cron setup, and CLAUDE.md (including the blocklist and style guide) — Claude Code wrote all of it while I talked to it over the CLI. A system that would normally take weeks of spec work, implementation, and debugging gets to a working state in one stretch of conversation.
Q. How do you keep the voice and structure aligned with the AI?
A. I fetched the past posts locally via the Blogger API and had Claude Code analyze the stylistic traits, section structures, and category tendencies, then saved that to memory. Memory persists across sessions, so there's no need to paste a long style guide into the prompt for every new post. Claude Code assembles articles as a continuation of its past self.
※ The steps in this post were verified at the time of writing (May 2026), but version updates to Claude Code, the Blogger API, and the various open-source pieces may mean they no longer work as written. If something doesn't work, let me know in the comments.
Wrap-up
The design decisions that mattered when automating a blog with Claude Code come down to four:
- Feed past posts to the AI and save the voice and category tendencies to memory: establish the continuity of "writing as a continuation of my past self" first
- Give up on "the LLM does it all": structure recent trends, your own interests, and the list of past posts in Python and feed them in from outside. Trying to write from the LLM's knowledge alone converges on the standbys
- Coordinate two sessions through a spec in git: for work spanning separate machines and repos, "nothing stalls" and "less API drift" pay off substantially. Treating a spec as a contract generalizes well beyond Claude Code
- A human presses the publish gate: stopping at the draft makes failure reversible, which makes it easy to experiment with AI generation. Not aiming for full automation is what ends up moving automation forward
The thing I want to emphasize is how fast it came together. The RSS scouting script, the MCP server, the Blogger API integration, the cron setup, CLAUDE.md (blocklist and style guide included) — Claude Code wrote all of it in one day. Spec work, implementation, and debugging that would normally take weeks moved in one stretch, just by talking over the CLI. Because you can run a loop of "explain what you want in words → a working implementation appears almost immediately," the cost of experimenting with design decisions drops dramatically.
Since then the rhythm has been: a draft lands on Friday night, I review it over the weekend, and it goes out. By not aiming for full automation and keeping "a human looks at it last" as the premise, I can widen what I delegate a little at a time.
If this post was useful, I'd be glad if you shared it on X (Twitter).
Related posts
- Generating AI Blog Images with a Local GPU [SDXL + IP-Adapter + img2img — Every Step of the Evolution, with Real Output] — a technical deep dive on building the image generation server mentioned in "Sticking point 2"
No comments:
Post a Comment