AI & Automation

How I broke my own AI content automation system (and what I'm building instead)

Edited by Jay AhnMay 15, 20269 min read1,755 words
How I broke my own AI content automation system (and what I'm building instead)

Six months ago I built an AI content automation system to run three niche blog sites end-to-end — n8n workflows, Claude as the writer, Postiz as the social distributor, Supabase as the store, Cloudflare as the runtime. The pitch I made to myself was the usual indie one: write the system once, let it run, harvest a small AdSense check at the end of each month while I work on the next thing.

Last week, one of the three sites got rejected by Google AdSense for "low-value content."

I am not surprised. I am also not going to pretend the rejection was unfair. Google saw exactly what was on the site, and what they saw was a textbook case of the thing they spent most of 2024 writing new policies to catch — Scaled Content Abuse, in their words. This post is the postmortem.

What I actually built

A short tour of the stack, because the rest of this won't make sense without it.

  • Three Next.js sites on Cloudflare Workers, each pointed at the same Supabase blog_posts table, filtered by a site column. Each site has its own home page, blog index, and /blog/[slug] route.
  • n8n running locally, with three workflows that share a common pattern: pick a trending topic, ask Claude to write a 1,800–2,400-word post in JSON, parse the result, insert into Supabase with is_published: true, then hand the post off to Postiz to schedule X / Instagram / Threads.
  • Claude Bridge, a small Node HTTP server I wrote so n8n's httpRequest nodes can talk to my locally installed claude.exe --print without dragging the Anthropic API key around. The Bridge is the part I rewrote the most.
  • A cron schedule running every two hours on weekdays, with what I thought were sensible per-site daily limits.

That last detail is where the rebuild story starts.

What broke

Three things, in order of how much they cost me to debug.

1. The Bridge was deadlocking on MCP stdio.

When claude.exe runs in a terminal, it auto-initializes the MCP servers configured in ~/.claude.json — in my case, four of them. When claude.exe runs as a subprocess of the Bridge, those same MCP initializers fight the Bridge for stdin/stdout, and the whole process hangs about a third of the way through the first generation. The n8n side sees ConnectionRefused and moves on to its graceful-failure branch. The graceful-failure branch returns success.

The relevant block in claude-bridge.js looks like this — the two flag lines in the middle are the entire fix:

const proc = spawn(CLAUDE_PATH, [
    '--print', '--model', resolvedModel,
    '--strict-mcp-config',
    '--mcp-config', '{"mcpServers":{}}',
    '--no-session-persistence',
], {
    windowsHide: true,
    stdio: ['pipe', 'pipe', 'pipe'],
    env: { ...process.env, NO_COLOR: '1' }
});

The Bridge log during a hang is — and this is the unsettling part — completely empty. The MCP initializers block in their own loop without writing anything to the parent process's stdio, so the Bridge has no event to log. n8n eventually sees curl: (28) Operation timed out on its side and falls through. The Bridge log only resumes after the fix, with the normal POST /claude → 200 (49322ms) entries showing actual generations completing in ~50 seconds.

The cost was about a week of "why are zero posts coming through, but n8n keeps saying it's fine?"

2. The daily limit gate did not actually gate.

I had two parallel workflows — Phase 8a and Phase 8b — each enforcing its own per-site daily limit. They each thought they were the only writer. On the day the bug surfaced, one site shipped eighteen posts. Eighteen. I had set the per-workflow cap at two; the actual cap was the sum across all workflows that touched the same site column, which is not a thing the workflows could see locally.

Here is the hour-of-day histogram for that site (distillfin, 2026-05-14 UTC), pulled straight from the created_at column:

00:00  # (1)
02:00  ## (2)
06:00  ## (2)
08:00  # (1)
10:00  ## (2)
12:00  ## (2)
14:00  ## (2)
16:00  ## (2)
18:00  # (1)
20:00  ## (2)
22:00  # (1)
              ─── 18 posts ───

Eighteen posts in 24 hours on a YMYL site that had been live for under three weeks. That is not a number a thoughtful editor produces. That is a number a cron job produces.

The fix was to centralise the gate at the query layer — one daily-count check that all writers consult — and set the limit lower. The cost was a day of post-density that pushed the site further into "scaled AI content" territory in front of an AdSense reviewer who happened to be looking that week.

3. The compliance surface was a vacuum.

This is the one that mattered.

The sites had no /about page that named anyone. No /contact. No /privacy. No editorial policy. No AI disclosure. The author byline on every post said "Editorial Team," which is technically a sentence but not the kind of sentence Google's reviewers find reassuring. The interactive disclaimer on each post said "for informational purposes only" without ever mentioning that the article was AI-assisted.

In other words: the sites looked exactly like the thing the Search Spam Policies update of March 2024 was written to flag. "Using generative AI tools or other similar tools to generate many pages without adding value for users" is the policy text. I had built something that matched the description line by line.

What Google actually saw

I want to be specific about this, because the rejection notice is short and the temptation is to read it as a black box. It isn't. Google's Search Quality Rater Guidelines are public, and they spell out exactly what the reviewers are looking at on a sample of any submitted site:

  • Who wrote this? (Self-evident byline + author page.)
  • Is the use of AI disclosed?
  • Does the content show first-person experience?
  • Is the publisher trustworthy enough for the topic? (Higher bar for YMYL — Your Money, Your Life.)

On all four, my answers were essentially "no." Two of the three sites were squarely in YMYL territory — finance on one, health/longevity on another — and I was running them with no credentials, no named author, no disclosed AI use, and three posts per day of generic synthesis.

The "low-value content" rejection wasn't a verdict about any individual article. It was a verdict about the operating model.

The one broken assumption

If I have to compress what I learned into a single sentence, it's this: the system was optimized for quantity in the hope that quantity would substitute for trust. It cannot.

That was the load-bearing assumption. Pull it out, and everything else I built collapses into the right shape. AdSense isn't gated on volume. Search isn't gated on volume. Audience isn't gated on volume. All three are gated on whether a reader (or a Quality Rater) can answer one question: do I believe this person, on this topic, on their evidence? Volume can't move that question. First-person evidence and a real author can.

The 484 auto-generated posts already on the sites aren't the problem so much as they are the proof. A human can publish 484 considered posts over years; an automation can do it in a quarter. The acceleration is the signal.

What I'm building instead

This is what I've been doing this week, in the order I did it.

  1. Three real author personas. One per site, each with a backstory, a Twitter handle, an about page, a domain email. Each post now carries the line "Edited by [name]" with a link to their bio. The schema.org markup emits a Person with @id and sameAs. (I'm one of those personas — the one on this site, in fact.)
  2. Five compliance pages, per site, in the same week. About, Contact, Privacy, Editorial Policy, AI Disclosure. The AI Disclosure page is the one I'd been avoiding writing for six months; it turned out to take less than an hour once I admitted what was actually true.
  3. Niche pivots on the two YMYL sites. The finance site is now a fintech-tools-and-public-data-visualization site. It does not give investment advice. The health site is now a productivity-and-habit-experiments site. It does not give medical advice. The domains are the same; the category surface is different.
  4. Noindex on 320-ish legacy posts whose categories contradict the new positioning. The pages stay reachable so external links don't break, but the sitemap drops them and the robots meta tag tells Google to stop indexing them. The two YMYL sitemaps shrank from 177 and 150 blog URLs to 0 and 3 respectively. That is, candidly, an embarrassing number to write down. It is also the truthful number.
  5. The automation pipeline switched to draft-only mode. The Supabase INSERT now sets is_published: false. The n8n workflows are inactive while I rebuild the prompt around the persona voice and the new editorial process. When they come back, they will produce drafts I read before they ship. Not the other way around.
  6. One human-written log per site per week. This post is the first one. The mechanic is: AI helps me research and outline; I write; AI helps me edit; I publish. If a post can't be defended in a one-paragraph "what I actually did" summary, it doesn't ship.

None of this is novel. Most of it is what serious independent publishers already do; the only thing I had to admit was that I wasn't one yet.

What's next

I'm publishing one of these logs every Tuesday for the next year, on each of the three sites. The logs document the rebuild — what worked, what didn't, what the data looked like. The next post here is going to be about the Bridge specifically, with the actual code, the actual stack trace, and the four flags I now run with by default.

I won't be reapplying to AdSense for at least another month, possibly two. There is no point reapplying with the same operating model and a fresh coat of paint; the reviewers see paint. The reapplication makes sense when the site has eight to ten posts that a reviewer can pick at random and read as something an actual human, with actual experience, would write. We aren't there yet. We will be.

If you have spent any time in this corner of the indie internet, you've seen this story before from someone else's blog. Now you've seen it from mine.

— Jay

ℹ How this was written: AI-assisted and edited by Jay Ahn. See our AI Disclosure and Editorial Policy for details. This article is for informational and educational purposes only and does not constitute professional advice. AI tools, automation platforms, and technology evolve rapidly — verify information independently before making decisions based on this content.
automationn8nclaudebuild-in-publicpost-mortemindie
SharePost on X