LLMs Love Markdown. CMSs Love Databases. I Built a WordPress Bridge Nobody Should Need.
A technical post-mortem on why WordPress's REST API is theoretically elegant but practically unusable for CI/CD, how SiteGround's security theater...
Or: The 29-Commit Journey from "This Should Be Easy" to "Why Does WordPress Hate Robots?"
The Premise (Which Turned Out to Be a Lie)
A friend—let's call him Ali G1—needed help with his site at aligstudios.com. He previously paid someone to migrate from Drupal to WordPress2.
That's it. One page. One menu item. Should take five minutes, right?
Except Ali gave me admin access to the WordPress dashboard and I looked at it. Really looked at it. The TinyMCE editor with its floating toolbars3. The "Add New Page" button that leads to a form with 47 fields, most of which I don't understand. The menu builder that's somehow both too simple (can't do anything complex) and too complicated (requires clicking through three nested screens). The Gutenberg block editor that's supposed to be modern but feels like using PowerPoint to write an essay.
And I thought: Hell no.
I could log in, click "Add New Page," paste content into that editor, fight with the formatting, figure out how to add a menu item (is it in Appearance? Settings? A plugin?), save it, realize the formatting broke, fix it, save again—
Or.
Or.
I could spend 30 minutes building a GitHub Actions workflow that converts Markdown to HTML and publishes via the WordPress REST API. Write once, automate forever. Never touch that dashboard again4.
So here's what I thought would happen: write some Markdown files5, push to GitHub, let Actions convert them to HTML, POST them to WordPress via the REST API6, done. Thirty minutes, tops. Maybe an hour if the API documentation is particularly WordPress-esque7.
Here's what actually happened: I spent 4+ hours fighting SiteGround's Anti-Bot AI8, rewrote the entire workflow to use SSH + WP-CLI, debugged six separate SSH implementation issues9, and ended up with a working system that bypasses HTTP entirely because—and I cannot stress this enough—WordPress is fundamentally hostile to automation.
This is that story. It's about WordPress, yes, but really it's about how we've built these baroque, self-important content management systems that are technically open-source and theoretically API-first but practically unusable for the exact use case they claim to support: automated content publishing10.
Act I: The Official Way (Or, How I Wasted My Afternoon)
15:08 - The Beginning
First commit. Project initialized. Created pages/ directory, added speaker-bio.md, wrote a clean README. The plan was elegant:
name: Publish to WordPress
on:
push:
paths:
- 'pages/**/*.md'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Convert Markdown to HTML
run: |
python3 -c "import markdown; print(markdown.markdown(open('pages/speaker-bio.md').read()))" > content.html
- name: Publish via REST API
run: |
curl -X POST https://yoursite.com/wp-json/wp/v2/pages \
-H "Authorization: Basic $(echo -n 'username:app_password' | base64)" \
-H "Content-Type: application/json" \
-d '{"title":"Speaker Bio","content":"'$(cat content.html)'","status":"publish"}'
142 lines of YAML. Clean. Straightforward. Used Application Passwords (WordPress 5.6+), which is the modern, security-conscious way to authenticate9. Hit the /wp-json/wp/v2/pages endpoint, which is the official way to create pages10.
Pushed it. Watched the Actions run. Got a 202 response.
Wait.
202?
17:36 - The First Sign Something Was Wrong
HTTP 202 is "Accepted" — which usually means "your request is being processed asynchronously." Except WordPress doesn't do asynchronous page creation. You POST to /wp-json/wp/v2/pages, it creates the page synchronously, returns 201 with the page data.
But I'm getting 202. And when I check the WordPress admin panel: no page.
I add logging. Check the response body:
<html>
<head><title>Security Check</title></head>
<body>
<div class="sgcaptcha">
Please verify you are human...
</div>
</body>
</html>
Oh.
Oh no.
17:40 - The Realization
SiteGround—my hosting provider, which is otherwise quite competent—has a feature called "Anti-Bot AI"9. It's infrastructure-level. It operates before your request reaches WordPress. It looks at:
- Request patterns
- IP address reputation
- User agent strings
- Timing between requests
- Probably your astrological sign
And it has decided that GitHub Actions—an IP range owned by Microsoft, running on Azure, making completely legitimate API requests with proper authentication—is a bot10.
The request never reaches WordPress. My Application Password never gets checked. WordPress never sees the request at all. It's blocked at the infrastructure layer—probably nginx or a WAF module—and redirected to a captcha page11.
19:14 - The Attempted Workarounds
At this point I've been at this for nearly 4 hours. I try everything:
Attempt 1: Use the Batch API
WordPress 5.6+ has a batch endpoint: /wp-json/wp/v2/batch. Maybe if I make fewer requests, the Anti-Bot AI will be less suspicious?
Nope. Still blocked.
Attempt 2: Better error handling
Added comprehensive logging, HTTP status checking, authentication verification. Great for debugging, useless for getting past the block.
Attempt 3: Disable SiteGround Security Plugin
WordPress has a SiteGround Security plugin. Disabled every setting. Didn't help—because the blocking happens before WordPress, at the server level.
Attempt 4: Whitelist GitHub IPs
SiteGround's Site Tools has a "Whitelist IPs" feature. Great! I'll just add GitHub's IP ranges—oh wait, GitHub Actions uses Azure's infrastructure, which is approximately 640,000 IP addresses12. And SiteGround's whitelist field has a character limit.
Attempt 5: Contact SiteGround Support
The Anti-Bot AI can apparently be disabled, but only by support. So I open a ticket:
"Hi, I'm trying to use the WordPress REST API with GitHub Actions for automated publishing. Your Anti-Bot AI is blocking all requests with HTTP 202 captcha redirects. Can you please disable it for my account or whitelist GitHub Actions IPs? Alternatively, can you provide guidance on how to authenticate automated requests so they're not flagged?"
I draft this ticket. Stare at it. Realize that even if they respond promptly (they won't), even if they're helpful (maybe?), even if they whitelist the IPs (GitHub will change them eventually)... this is not a sustainable solution11.
The "official" way—the REST API, the Application Passwords, the documented endpoints—is functionally useless for CI/CD with managed WordPress hosting.
Act II: The Pivot (Or, What If We Just Bypass HTTP Entirely?)
20:39 - The Insight
I'm staring at my workflow file. Fourth hour of this. Should have been done in thirty minutes. And I have this thought:
What if we bypass HTTP entirely?
SiteGround's Anti-Bot AI protects HTTP/HTTPS endpoints. But I have SSH access to the server12. And WordPress has WP-CLI11. And WP-CLI can do everything the REST API can do, except it runs on the server, so there's no HTTP request to block.
GitHub Actions can SSH into servers. That's a solved problem. CI/CD systems have been SSHing into production servers since before "CI/CD" was a term. We have battle-tested patterns:
- SSH keys (better than passwords)
- Passphrase-protected keys (even better)
- Expect scripts to handle passphrases in automation
- SSH agents to cache credentials
This should work.
20:42 - The First SSH Implementation
I rewrite the workflow:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
eval "$(ssh-agent -s)"
expect << 'EOF'
spawn ssh-add ~/.ssh/id_ed25519
expect "Enter passphrase"
send "${{ secrets.SSH_PASSPHRASE }}\r"
expect eof
EOF
- name: Publish via WP-CLI
run: |
ssh user@yoursite.com "wp post create \
--post_type=page \
--post_title='Speaker Bio' \
--post_content='<html content here>' \
--post_status=publish"
From 384 lines to 125 lines. Simpler. More direct. Should be faster.
I push it.
It fails.
20:42 - 20:57: Six SSH Implementation Challenges
What follows is 15 minutes of increasingly specific failures. Each one makes sense in retrospect. Each one was maddening in the moment.
Challenge 1: SSH Key Newlines (20:42)
GitHub Secrets stores your SSH key as a string. When you echo it to a file, newlines get mangled12. The key looks fine in the logs (which are redacted anyway), but OpenSSH rejects it.
Fix: echo "${{ secrets.SSH_PRIVATE_KEY }}" | tr -d '\r' > ~/.ssh/id_ed25519
Challenge 2: Expect Script Paths (20:45)
The expect script needs the full path to the SSH key. ~/.ssh/id_ed25519 doesn't expand in the expect context because expect is its own interpreter11.
Fix: Use $HOME/.ssh/id_ed25519 or /home/runner/.ssh/id_ed25519
Challenge 3: YAML vs Heredoc (20:47)
I tried using a heredoc to define the expect script in-line:
run: |
expect << 'EOF'
spawn ssh-add ~/.ssh/id_ed25519
expect "Enter passphrase"
send "${{ secrets.SSH_PASSPHRASE }}\r"
expect eof
EOF
YAML sees << and gets confused12. Is it a YAML thing? A bash thing? Both? The error message is useless.
Fix: Use inline expect commands instead of heredoc:
run: expect -c "spawn ssh-add ~/.ssh/id_ed25519; expect \"Enter passphrase\"; send \"${{ secrets.SSH_PASSPHRASE }}\r\"; expect eof"
Challenge 4: RSA vs ED25519 (20:54)
My workflow was looking for ~/.ssh/id_rsa. My actual key is ED2551911. OpenSSH couldn't find the key.
Fix: Change all id_rsa references to id_ed25519
Challenge 5: SSH Agent Persistence (20:56)
This was the big one. I start the SSH agent in one step:
- name: Setup SSH
run: eval "$(ssh-agent -s)"
Then try to use it in the next step:
- name: Publish
run: ssh user@host "wp post create..."
Doesn't work. The SSH agent isn't running in the second step.
Why? GitHub Actions runners are ephemeral. Each step runs in a new shell session12. The SSH agent is a process that exports environment variables (SSH_AUTH_SOCK, SSH_AGENT_PID). Those variables don't persist across steps.
Fix: Restart the SSH agent and re-add the key in every step that needs SSH:
- name: Publish via SSH
run: |
eval "$(ssh-agent -s)"
expect -c "spawn ssh-add ~/.ssh/id_ed25519; expect \"Enter passphrase\"; send \"${{ secrets.SSH_PASSPHRASE }}\r\"; expect eof"
ssh user@host "wp post create..."
Challenge 6: Content Escaping (20:57)
Finally, I need to pass HTML content to WP-CLI over SSH. The content has:
- Double quotes
- Single quotes
- Newlines
- Probably some other bash/SSH special characters
Three layers of escaping: YAML → Bash → SSH → WP-CLI11.
Fix: Base64 encode the content, pass it over SSH, decode it on the server:
- name: Publish
run: |
CONTENT=$(base64 -w 0 content.html)
ssh user@host "echo $CONTENT | base64 -d | wp post create --post_content=-"
20:57 - Success
After those six challenges, the workflow runs.
It SSHes into the server. Runs wp post create. The page appears in WordPress.
No HTTP 202. No captcha. No Anti-Bot AI.
Total time for SSH implementation: 1.5 hours.
Total time spent on REST API: 4+ hours (and it never worked).
Act III: The Uncomfortable Truth About CMSs
What This Reveals About WordPress
WordPress markets itself as:
- Modern: "We have a REST API! JSON endpoints! Webhooks!"
- Open: "Open source! Customize anything! Build what you want!"
- Developer-Friendly: "Extensive documentation! Rich ecosystem!"
But in practice, for the specific use case of automated publishing:
- The REST API is theater
It exists. It's documented. It technically works. But managed hosts block it, rate limit it, or wrap it in so much security that you can't use it for automation12.
-
The "official" way is not the reliable way
SSH + WP-CLI is less documented, requires more setup, and feels hacky. But it's more reliable because it bypasses all the HTTP security theater. -
WordPress optimizes for the wp-admin experience
Everything else—APIs, CLI, programmatic access—is an afterthought. You can tell because the happy path is "log into wp-admin, click 'Add New Page', paste your content, click Publish." Any deviation from that is swimming upstream.
What This Reveals About Managed Hosting
SiteGround is not unique here. Most managed WordPress hosts do some variation of:
- Aggressive bot detection
- Rate limiting on API endpoints
- Caching that breaks dynamic features
- "Security" plugins you can't disable
- Infrastructure-level blocking you can't configure
They're optimizing for:
- Shared hosting economics: Can't let one customer's API calls bog down the server
- Security: Automated requests could be attacks, so block them all
- Support load: Easier to block automation than to teach customers how to secure it
What they're not optimizing for: developers who want to use WordPress programmatically11.
The AI Agent Problem
Here's where this gets interesting for 2025 and beyond:
If you're building AI agents that interact with CMSs—and people are—WordPress is a nightmare:
- No reliable API: REST API is blocked by hosting security
- No stable SDK: The PHP internals change; WP-CLI is more stable but requires SSH
- No machine-readable errors: Get a 202? Could be success, could be a captcha
- No cost-effective testing: Can't spin up WordPress in a container to test; need real hosting12
For AI agents to work with CMSs, you need:
- Stable APIs (WordPress: yes, in theory)
- That actually work (WordPress: no, in practice)
- With clear error messages (WordPress: hahaha no)
- And predictable behavior (WordPress: lol)
Compare to:
- Notion: Stable API, great docs, works reliably
- Airtable: Same
- GitHub: You're reading a blog post about how GitHub Actions SSH'd into a server, so clearly they have their shit together
- Supabase/Firebase: Database-as-a-service, direct API access, no hosting weirdness
WordPress is from a different era. It's a LAMP stack app that got a REST API bolted on in 201611. It was never designed for programmatic access, and it shows.
The Markdown vs. Database Problem
But here's the deeper issue: LLMs love Markdown. CMSs love proprietary database schemas.13
When you write in Markdown:
- ✅ It's version-controllable (Git)
- ✅ It's portable (any editor, any platform)
- ✅ It's readable by both humans and LLMs
- ✅ It's composable (concatenate files, split sections)
- ✅ It's transformable (Markdown → HTML → PDF → whatever)
- ✅ It's future-proof (plain text never dies)
When you write in WordPress:
- ❌ Content lives in
wp_poststable with HTML soup - ❌ Metadata scattered across
wp_postmeta(key-value hell) - ❌ Media in
wp_posts(wait, media is posts?) - ❌ Relationships in other tables (categories, tags, taxonomies)
- ❌ Custom fields might be JSON, might be serialized PHP, might be comma-separated strings
- ❌ Good luck exporting this cleanly14
For AI agents, this is a dealbreaker.
An LLM can generate Markdown in its sleep. Give it context, it writes clean Markdown with proper heading hierarchies, lists, links, code blocks—all the structure you need.
But generating content for WordPress? The LLM needs to:
- Understand the wp_posts schema (which fields are required? which are auto-generated?)
- Know how to structure HTML that WordPress won't mangle
- Handle metadata separately (featured images, SEO fields, custom fields)
- Deal with shortcodes (WordPress's bizarre template language)
- Pray that none of the 60,000 plugins have hijacked the post creation hooks
It's not impossible. But it's friction. And friction kills automation.
This is why Git-based CMSs are winning for AI workflows:
- Write Markdown files
- Push to Git
- Static site generator builds the site
- Content and code in the same repo
- LLMs can read/write directly to files
- No database, no API, no hosting bullshit
WordPress offers features—plugins, themes, a GUI. But for AI-driven content workflows, those features are baggage15.
Why This Matters
We're moving toward a world where:
- Content is created by AI
- Content is published via automation
- Content management is code, not clicking buttons
WordPress's market share (43% of all websites!) means a lot of content lives in WordPress. But WordPress's architecture means that content is trapped there16.
You can't easily:
- Sync content between WordPress and other systems
- Generate content with AI and publish it programmatically
- Manage WordPress content from modern tools (Notion, Linear, etc.)
- Use WordPress as part of a larger automated workflow
Unless you're willing to SSH into the server and use WP-CLI, which is exactly what I ended up doing17.
Act IV: The Solution (And Why It Feels Wrong)
What I Built
The final workflow:
- Trigger: Push Markdown files to
pages/directory - Convert: Python converts Markdown to HTML
- SSH: GitHub Actions SSHs into SiteGround server
- WP-CLI: Creates/updates pages directly via WP-CLI
- Verify: Checks that the page exists and is published
No HTTP requests. No REST API. Just SSH + WP-CLI12.
- SSH key setup with passphrases
- Ephemeral runner SSH agent management
- Title extraction from Markdown
- Slug-based deduplication (check if page exists, update vs. create)
- Content escaping (base64 encoding)
- Comprehensive error handling
- Clear logging for debugging
Features:
- ✅ Reliable: Bypasses all HTTP security layers
- ✅ Fast: No rate limits, no timeouts
- ✅ Powerful: Full WP-CLI feature set (cache clearing, menu management, etc.)
- ✅ Idempotent: Check if page exists by slug, update instead of duplicate
- ✅ Debuggable: Comprehensive logging, clear error messages
The code is open source: https://github.com/augchan42/wp-ali-public
Why It Feels Wrong
Because this shouldn't be necessary.
The REST API should work. Hosting providers should whitelist legitimate automation. WordPress should provide better tooling for CI/CD11.
But instead, we're in this weird place where:
- The "modern" solution (REST API) is blocked
- The "legacy" solution (SSH + WP-CLI) actually works
- The "official" documentation points you toward the thing that doesn't work
- The "unofficial" approach requires reading blog posts from people who've already suffered through this
It's a pattern you see a lot in mature open-source projects: the official way is aspirational, the real way is tribal knowledge15.
Who This Is For
If you're:
- Running a WordPress site and want to publish via Git
- Building AI agents that need to write to WordPress
- Automating content publishing and hitting security blocks
- Using managed WordPress hosting (SiteGround, WPEngine, etc.)
- Frustrated with the REST API and need something that actually works
Then this bridge is for you14.
If you're:
- Building a new site and choosing a CMS
- Considering WordPress for programmatic content management
- Evaluating CMSs for AI agent compatibility
Then don't use WordPress15. Use Notion, use Airtable, use Contentful, use any headless CMS built after 2015. They have APIs that actually work.
Conclusion (Or, What I Learned Spending 5 Hours on a 30-Minute Task)
The Technical Lessons
-
Layer understanding matters: The Anti-Bot AI blocks at the infrastructure layer, before WordPress. No amount of WordPress configuration can fix that.
-
The official way isn't always the reliable way: REST API is official but blocked; SSH + WP-CLI is "legacy" but works.
-
GitHub Actions runners are ephemeral: SSH agents don't persist between steps. Restart them in every step.
-
Three-layer escaping is a nightmare: YAML → Bash → SSH → CLI means base64 encoding is your friend.
-
Direct server access wins for automation: No HTTP means no rate limits, no security theater, no timeouts.
The Philosophical Lessons
-
CMSs are hostile to automation
They're built for humans clicking buttons, not machines making API calls. -
Security theater breaks legitimate use cases
SiteGround's Anti-Bot AI blocks GitHub Actions (Microsoft/Azure IPs, legitimate API calls) while actual bots use residential proxies and get through fine16.
-
WordPress is from a different era
Born in 2003, designed for LAMP stacks and server-side rendering. The REST API and modern tooling are retrofits, and it shows. -
The AI agent future requires better CMSs
If GitHub Actions can't reliably publish to WordPress, how will AI agents? We need CMSs designed for programmatic access from the ground up17.
The Meta Lesson
When you fight the system for 4 hours and then bypass it entirely in 1.5 hours, you learn something: sometimes the solution is not using the official way.
The REST API is elegant in theory. SSH + WP-CLI is ugly in practice. But ugly-and-working beats elegant-and-broken18.
Appendix: The Implementation
Here's the GitHub repository—a sanitized snapshot of the final working solution19.
What you'll find:
- ✅ Working GitHub Actions workflow (SSH + WP-CLI)
- ✅ Complete setup and troubleshooting docs
- ✅ All the SSH agent management patterns
- ✅ Markdown → HTML → WordPress pipeline
What you won't find:
- ❌ The 29-commit journey (security reasons)
- ❌ My 4 hours debugging SiteGround's Anti-Bot AI
- ❌ The failed REST API attempts
But the blog post you just read is the commit history, narrativized. From "this should be easy" → "why does WordPress hate robots" → "fine, SSH" → "oh god, ephemeral runners" → "it works."
The code is MIT licensed. Use it. Fork it. Improve it. Or better yet: build a CMS that doesn't need these kinds of bridges in the first place20.
Thanks for reading. If you've made it this far, you either really care about WordPress automation, or you're one of those people who reads all the footnotes first and then goes back to the main text. Either way: solidarity.
Footnotes
-
Not an alias. That's actually his name. Yes, like the Sacha Baron Cohen character. He says it's for marketing. I think it's working. ↩
-
Drupal to WordPress is like escaping from one maximum-security CMS prison to another. Drupal is the one where you need a PhD to configure a content type. WordPress is the one where everything is easy until you need to do anything programmatically, at which point you discover the bars were just better hidden. ↩
-
TinyMCE: Because what developers really wanted in 2025 was to write content in an editor that hasn't meaningfully changed since 2008, still produces HTML that looks like it was written by a cat walking across a keyboard, and has a "paste from Word" button as if that's a feature and not an admission of defeat. ↩
-
This is the kind of decision-making that seems rational at 3 PM on a Tuesday and deeply questionable when you're debugging SSH key newlines at 8 PM. But we all have our principles. Mine apparently include "I will spend 10 hours automating a 5-minute task rather than click buttons in a bad UI." It's the programmer's disease: the conviction that automation is always worth it, even when the math clearly says it's not. ↩
-
Because in 2025 we're still pretending that everyone wants to write content in a WYSIWYG editor that mangles your HTML and adds seventeen
<span>tags with inline styles nobody asked for. Markdown is what humans write. WordPress is what marketers think humans want. ↩ -
The REST API! Finally! WordPress joined the modern web circa 2016 when they shipped WP-JSON endpoints. Only took them 13 years after launch to discover that maybe—just maybe—people might want to interact with their CMS programmatically without learning PHP and the WordPress Loop™ (which isn't really a loop, it's more like a prayer to the database that hopefully returns post content in the right order). ↩
-
WordPress documentation exists in this quantum state where it's simultaneously comprehensive and completely useless. You can find detailed docs about every filter hook since 2.7, but good luck finding a straightforward answer to "how do I publish a page via API without getting a 401." ↩
-
More on this later, but preview: SiteGround's "Anti-Bot AI" is like TSA security theater except instead of confiscating your shampoo, it blocks every legitimate API request from GitHub Actions while presumably letting actual bots through because they're smart enough to use residential proxies. ↩
-
Including the delightful discovery that GitHub Actions runners are ephemeral, which means your SSH agent dies between steps, which means you have to restart it and re-add your key with an expect script in every single step, which is the kind of developer experience that makes you question your career choices. ↩ ↩2 ↩3
-
And by extension: AI agents. If GitHub Actions—which is literally just running Python scripts with curl—can't reliably talk to WordPress, how exactly are we expecting AI agents to do it? Spoiler: we're not. WordPress is for humans clicking buttons in wp-admin. Everything else is cosplay. ↩ ↩2 ↩3
-
Application Passwords are WordPress's answer to "what if we let users create API tokens that aren't their actual password?" Revolutionary stuff, only 40 years after every other system figured this out. To their credit, once you figure out where the setting is buried (hint: it's in the user profile, except when your hosting provider disables it for "security"), they actually work pretty well. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
There's also
/wp-json/wp/v2/postsfor blog posts, and approximately 47 other endpoints for custom post types, media, comments, categories, tags, users, and probably your grandmother's cookie recipes. The API is comprehensive. It's the epitome of "we built the thing you asked for" without asking "but should we have?" ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
This is not a coincidence. Markdown is plain text. LLMs are trained on plain text. They can read it, write it, and reason about it naturally. Database schemas? Those require queries, ORMs, understanding foreign keys and join tables, and hoping the CMS didn't decide to serialize arrays as PHP strings in a TEXT column (WordPress does this, btw). ↩
-
WordPress has export tools. They generate XML. The XML is valid but deeply weird—it's not quite RSS, not quite Atom, it's "WordPress eXtended RSS" (WXR). It includes all your content, but also all the WordPress-specific IDs and relationships. Want to convert it to Markdown? Better hope your conversion tool handles every edge case of WordPress's 20-year schema evolution. ↩ ↩2
-
And yes, there are headless WordPress setups where you decouple the frontend. But you're still stuck with WordPress's database schema on the backend. You've just added complexity (separate frontend + backend) without solving the fundamental problem: content is trapped in a database instead of living as portable, version-controlled files. ↩ ↩2 ↩3
-
Getting content out of WordPress is easier—the REST API works fine for reading. It's writing that's the problem. And if you can't reliably write to WordPress via API, you can't build AI agents that manage WordPress sites, you can't build CI/CD pipelines that publish to WordPress, you can't build integrations that sync content between WordPress and other systems. You're stuck logging into wp-admin like it's 2010. ↩ ↩2
-
And which I've now open-sourced at https://github.com/augchan42/wp-ali-public so you don't have to spend 5 hours debugging SSH key newlines and expect scripts. You're welcome. ↩ ↩2
-
This is a lesson I keep relearning. Beautiful abstractions that don't work are worse than ugly hacks that do. Python's
subprocessis a terrible API—seriously, go read the docs, it's a mess—but it works. JavaScript Promises are an elegant abstraction, butasync/awaitis the ugly syntax that makes them usable. WordPress REST API is clean and RESTful and blocked by hosting security; SSH + WP-CLI is old-school and imperative and gets the job done. ↩ -
The original repo had all 29 commits showing the full journey from REST API failure to SSH success. But it also had sensitive credentials scattered throughout the history (SSH keys, hostnames, API tokens). Rather than trying to rewrite Git history properly, I made a clean copy. Security lesson: never commit secrets, even temporarily. Once it's in history, it's there forever. ↩
-
And if you do, let me know. I'll write a blog post about it. Without footnotes. Promise. ↩