Writing technical articles is harder than it sounds.
The topic. The research. The structure. The references.
Before writing a single sentence of the actual piece, there is already a lot of work. By the time you have done it, you are half-tired before you start.
I wanted to fix this. So I built a pipeline that automatically generates draft material for articles — using Claude Code to move fast, and GitHub Actions to run it daily.
The total build time was roughly half a day.
What I built
The goal was not to auto-publish articles.
It was to build a stockpile of article starters — background context, related keywords, structural outlines — so that when I sit down to write, the hardest part is already done.
The architecture:
HackerNews / Dev.to → keyword collection (daily 08:00)
↓
SQLite (keyword inventory)
↓
Gemini Flash-Lite → surrounding context generation (daily 09:00)
↓
drafts/ → auto-committed to GitHub
↓
Human reviews → writes → publishes
The key constraint: AI generates the material, not the article itself. The writing stays with me.
Keyword collection
HackerNews uses the Algolia API. Dev.to has an official API. Both are auth-free, which keeps the setup lightweight.
The goal at this stage was not to build a comprehensive collection system. It was to get a steady, small flow of relevant topics arriving every day.
SQLite for inventory management
The database is SQLite. For a personal pipeline, there is no reason to run a separate database server. The SQLite file lives in the repo — which also means the keyword inventory has full version history.
CREATE TABLE keywords (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
keyword TEXT NOT NULL,
category TEXT NOT NULL,
used INTEGER DEFAULT 0,
fetched_at TEXT DEFAULT (datetime('now'))
);
CREATE TABLE themes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
keyword_id INTEGER REFERENCES keywords(id),
title TEXT NOT NULL,
slug TEXT NOT NULL,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT (datetime('now'))
);
The used flag prevents the same keyword from generating multiple drafts. A monthly purge job clears out old entries.
Day-of-week rotation
Generating the same category of content every day gets monotonous. I split categories by day:
- Monday: Tech
- Tuesday: AI
- Wednesday: Career
- Thursday: Dev process
- Friday: Product
- Weekends: lighter topics
Each run pulls up to 5 unused keywords from that day's category. The cap matters — more than that and the folder becomes a pile nobody reads.
GitHub Actions for full automation
- 08:00 — collect keywords
- 09:00 — generate draft material
Generated files are committed and pushed automatically. When I sit down to write, drafts are already waiting. I pick a topic that looks promising and go from there.
How I used Claude Code
The entire build happened through conversation with Claude Code.
- Framing — Talked through a vague requirement until the spec was concrete
- Issue creation — Claude Code broke the work into GitHub Issues: fetch / generate / pipeline / DB / schedule
- Implementation — Generated scripts and GitHub Actions workflows
- Debugging — Ran it live, showed the errors, iterated until it worked
Step 4 was the most valuable. Getting from error message to fix in seconds — rather than searching for an hour — changes the pace of the entire build.
What actually broke
1. Vertex AI returning 403
First live run failed. Pasted the error into Claude Code. It identified BILLING_DISABLED and diagnosed the cause immediately: billing was not enabled on the GCP project.
2. Workload Identity Federation is scoped per repository
Copied a working config from another repo. It did not work. The workloadIdentityUser binding was scoped to the original repo. New repos need explicit registration:
gcloud iam service-accounts add-iam-policy-binding <SA> \
--role=roles/iam.workloadIdentityUser \
--member="principalSet://iam.googleapis.com/<POOL>/attribute.repository/<owner>/<repo>"
3. Japanese titles vanish when slugified
A standard replace(/[^\w\s-]/g, "") strips almost everything from a Japanese title. Fix: mix the database primary key into the slug from the start.
4. LLMs treat management labels as semantic content
I prefixed titles with Zenn: as an internal tag. The model read it as topic direction and generated off-target drafts. Strip management labels before passing anything to a model.
5. Swallowing errors produces silent empty output
Early on, API errors returned empty strings. Result: blank drafts committed as successful output. Better: throw on error, do not save empty output, do not mark keywords as used on failure.
Cost ceiling
Gemini 2.5 Flash-Lite at 5 drafts per day costs a few dollars per month at most.
But automated pipelines calling generative APIs need a hard ceiling. GCP budget alerts notify — they do not stop charges. I added a Cloud Function that disables the billing path when the monthly budget is exceeded. Build the stop yourself.
Why AI did not write the articles
I tried it. The output had hallucinated citations, placeholder text, and plausible-but-unverified claims. Not safe to publish directly.
As background research, keyword mapping, and structural outlines — more than good enough.
AI builds the inventory. The human does the writing and verification.
Since drawing that line, starting an article became significantly easier. I am not staring at a blank page. I am choosing from what already exists.
What this taught me
AI coding assistants make the cycle from vague idea to working system faster — not just the code writing part.
But the decisions were still mine.
What to build. What to automate. Where the human stays in the loop. What happens when it fails.
As tools become more capable, those design decisions become more important, not less.
This is an English adaptation of an article originally published in Japanese on Zenn.
Top comments (0)