We Built an AI Publishing Pipeline in One Afternoon (Here’s What Actually Happened)

Context

The goal was simple: give an AI operator the ability to write a blog post and publish it to a live WordPress site without any human clicking through a browser. Draft to live in one command.

What actually happened was a two-hour debugging session that produced a working pipeline — and five distinct lessons about the gap between “this should be easy” and “this actually works.”


What We Built

The architecture is straightforward:

1. AI drafts a post in Markdown, saves it locally

2. Markdown is uploaded to the server via SCP

3. A Python script on the server converts Markdown to HTML and calls WP-CLI to publish

4. WP-CLI creates the post directly in WordPress via the database — no browser, no API auth

The finished pipeline runs end-to-end from a single command. The AI writes, converts, transfers, and publishes. Human role: approve the draft before it goes.

That’s the clean version. Here’s what the path to get there actually looked like.


What Broke (or Almost Broke)

1. The WordPress Application Password Didn’t Exist

The obvious first approach was the WordPress REST API with an Application Password. Clean, standard, well-documented.

Except Application Passwords weren’t visible in the WordPress admin. Either a security plugin was suppressing them or the feature wasn’t enabled. We spent time trying to diagnose it before deciding to route around it entirely via SSH + WP-CLI. Sometimes the documented path is blocked and the less-obvious path is cleaner anyway.

Lesson: When the official integration point isn’t available, go one layer deeper. SSH + WP-CLI is more powerful than the REST API for server-side operations anyway.

2. SSH Was on a Non-Standard Port

Port 22 timed out. The server wasn’t listening there. Shared and cloud hosting environments routinely move SSH to non-standard ports as basic security hygiene — but it’s not always prominently documented.

We scanned common alternate ports and found it on 65002. Five minutes of port scanning saved a long support ticket.

Lesson: If SSH times out on 22, don’t assume SSH is unavailable. Scan 65002, 22022, 2222 before giving up.

3. The CLI PHP Version and the Web PHP Version Were Different

This one is a classic that still catches people. The server’s web environment was running PHP 8.x — confirmed by the live site functioning correctly. The CLI default was PHP 7.2. WP-CLI runs in the CLI environment.

So when WP-CLI tried to load WordPress, it hit plugins with syntax that PHP 7.2 couldn’t parse — a legitimate PHP 8.x feature that looks like a syntax error to older versions. WordPress reported a “critical error.” Everything looked broken. Nothing was actually wrong with WordPress.

The fix: explicitly specify the PHP 8.x binary when invoking WP-CLI.

“`bash

/path/to/php8x/bin/php /usr/local/bin/wp –path=/path/to/wordpress post create …

“`

Lesson: On shared hosting, always confirm which PHP version your CLI is running vs. which one the web server uses. They are often different. When using WP-CLI, prefix with the correct PHP binary explicitly.

4. Shell Escaping Made Inline Post Content Impossible

The initial approach was to pass the HTML post content directly as a shell argument to WP-CLI. This works for short strings. It falls apart completely for 7,000 characters of HTML containing quotes, apostrophes, angle brackets, and special characters.

Every approach to escaping that content inline — double quotes, single quotes, heredocs — introduced a new class of problem. The content got mangled, truncated, or caused syntax errors in the shell.

The fix: write the content to a temp file on the server, then have the Python script pass it programmatically via `subprocess` rather than through a shell string. Python’s `subprocess` module passes arguments as a list, bypassing shell interpretation entirely.

Lesson: Never try to pass large, complex content through a shell string. Write it to a file or pass it programmatically. Shell escaping at scale is a maintenance nightmare.

5. `/tmp` on Your Local Machine Is Not `/tmp` on the Server

An early version of the script wrote the converted HTML to `/tmp/post.html` and then tried to use it. The file existed locally. The WP-CLI command ran on the server. The server had no `/tmp/post.html`.

This is obvious in retrospect. It’s easy to lose track of execution context when you’re moving quickly between local scripts and remote commands.

Lesson: When working across local and remote environments, be explicit about where every file lives at every step. Draw the boundary clearly before you start.


What We Learned

The “simple” path is often blocked. Application Passwords, standard SSH ports, default PHP versions — any of these might not be what you expect. Build resilience into the integration by knowing the fallback at each layer.

Python’s subprocess is the right tool for complex server-side automation. Shell scripts are fast to write and fragile at scale. Python with subprocess passes arguments cleanly, handles content of arbitrary length and complexity, and gives you real error handling.

AI-to-server pipelines need explicit version pinning. PHP version, WP-CLI version, Python version — every layer of the stack needs to be explicit, not assumed. Assumptions fail silently in production and loudly at the worst time.


What We Changed

  • Dropped the REST API approach entirely — SSH + WP-CLI is more direct and more powerful
  • Moved all content passing to a Python subprocess call instead of shell strings
  • Pinned the PHP binary explicitly in all WP-CLI invocations
  • Stored SSH host, port, PHP path, and WP path in long-term memory so the pipeline is reproducible across sessions

Takeaways

  • If SSH times out on 22, scan 65002 and 22022 before assuming SSH is unavailable.
  • Always check CLI vs. web PHP version on shared hosting. They are often different.
  • Pass complex content via file + subprocess, not shell string interpolation.
  • `/tmp` is local. Always know which machine your commands are executing on.
  • When the official integration point is blocked, go one layer deeper. It’s usually cleaner.
  • The pipeline that works is the one you can reproduce tomorrow from memory (or from documented config). Write it down.

*AI Field Notes is a series documenting real-world AI deployments, governance experiments, and operational lessons from the field. All environments and details are generalized to protect organizational and individual privacy.*


Related Posts

1 thought on “We Built an AI Publishing Pipeline in One Afternoon (Here’s What Actually Happened)”

  1. Pingback: Connecting AI to My Hosting Account (What Broke and What We Learned) – The AI Field Notes

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top