<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/" >

<channel>
	<title>Macronimous Blog</title>
	<atom:link href="https://www.macronimous.com/blog/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.macronimous.com/blog</link>
	<description>Web design, web programming, Mobile apps, Opensource , SEO etc</description>
	<lastBuildDate>Tue, 29 Sep 2026 12:28:23 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</generator>
	<item>
		<title>Why Is My Writing Flagged as AI? We Used Dashes Before the Machines Did</title>
		<link>https://www.macronimous.com/blog/why-is-my-writing-flagged-as-ai/</link>
					<comments>https://www.macronimous.com/blog/why-is-my-writing-flagged-as-ai/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Thu, 01 Oct 2026 07:45:32 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Content Marketing]]></category>
		<category><![CDATA[AI content]]></category>
		<category><![CDATA[Ai Slop]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[Content writing]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5366</guid>

					<description><![CDATA[<p>Why is my writing flagged as AI? Because detectors and suspicious readers judge surface habits: dashes, tidy lists, even rhythm, polite openings. Models learned every one of those habits from human writers, so careful human writing now looks machine-made. Deleting the habits doesn&#8217;t fix it. What proves you wrote it is a clear position, details [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/why-is-my-writing-flagged-as-ai/">Why Is My Writing Flagged as AI? We Used Dashes Before the Machines Did</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Why-Is-My-Writing-Flagged-as-AI-We.jpg"><img fetchpriority="high" decoding="async" width="1672" height="941" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Why-Is-My-Writing-Flagged-as-AI-We.jpg" alt="Why Is My Writing Flagged as AI We" class="aligncenter size-full wp-image-5371" /></a>
<div class="mac-direct-answer">
<p><strong>Why is my writing flagged as <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">AI</a>?</strong> Because detectors and suspicious readers judge surface habits: dashes, tidy lists, even rhythm, polite openings. Models learned every one of those habits from human writers, so careful human writing now looks machine-made. Deleting the habits doesn&#8217;t fix it. What proves you wrote it is a clear position, details only a practitioner would know, and a last pass in your own words.</p>
</div>
<h2>The dash was ours first</h2>
<p>The first post on this blog went up on 19 February 2008. There are 372 of them now. I&#8217;ve written a lot of them, and I have a habit: I use dashes – not always the proper long one, often just the short one with a space on either side, because that&#8217;s what the keyboard gives you.</p>
<p>Go and look at December 2013. One post is titled &#8220;<a href="https://www.macronimous.com/blog/does-cdn-help-seo/">Does CDN help SEO? – CDN, Google, Page speed and ranking factors</a>.&#8221; Another one, the same month, has a dash in its title too. ChatGPT was nine years away.</p>
<p>Today a dash in a blog post gets read as a machine&#8217;s fingerprint. So do neat lists, tidy paragraphs and polite openings. Which is a bit rich, because the machines learned all of that from people like us (and, I suppose, from us). We even published a piece this April about <a href="https://www.macronimous.com/blog/emdash-vs-wordpress/">a CMS that is actually called EmDash</a>, which didn&#8217;t help.</p>
<p>So here&#8217;s the odd new job. It isn&#8217;t enough to write as a human anymore. You have to write as a human who can&#8217;t be mistaken for a machine. Human-human, if you like.</p>
<h2>So why is my writing flagged as AI?</h2>
<p>Short answer: because you write the way models were taught to write, and they were taught by people like you. So the first thing everybody tries is scrubbing out the habits. It&#8217;s the obvious move, and you can&#8217;t win that way.</p>
<p>You take out the dashes, and then someone says the real giveaway is the word &#8220;delve,&#8221; so you take that out, and then it&#8217;s groups of three, and then it&#8217;s any sentence shaped like &#8220;it&#8217;s not this, it&#8217;s that,&#8221; and then it&#8217;s being too polite, and by the time you&#8217;ve removed every habit a model picked up from human writers, which is all of them, because that&#8217;s where it got them, what&#8217;s left is a flat, careful page that sounds like nobody at all, which is, of course, exactly how a machine sounds.</p>
<p>The list of tells changes every few months anyway. Dashes were the 2024 tell. I don&#8217;t know what next year&#8217;s will be, and neither does anyone selling you a checker.</p>
<p>About those checkers. A Stanford team ran seven popular AI detectors over 91 essays written by people for the TOEFL (Test of English as a Foreign Language). The detectors wrongly flagged <a href="https://arxiv.org/abs/2304.02819" target="_blank" rel="noopener noreferrer">about 61 percent of those human essays as AI-written</a>, while scoring essays by American eighth graders almost perfectly. That paper is from 2023 and the tools have changed since, so treat the number as a warning more than a measurement. Still. We write from Coimbatore, India, for readers in the US, the UK and Australia. You can guess which pile we&#8217;d land in. If you&#8217;ve been falsely accused of using AI, this is probably why, and it says nothing about your writing.</p>
<p>And yet the worry behind all this is fair. Merriam-Webster made <a href="https://www.merriam-webster.com/wordplay/word-of-the-year" target="_blank" rel="noopener noreferrer">&#8220;slop&#8221; its word of the year for 2025</a>. People aren&#8217;t imagining the flood. They&#8217;re just using the wrong test to spot it.</p>
<h2>Yes, I use AI to write. Here&#8217;s how.</h2>
<p>I&#8217;d rather say this plainly than have you wonder. A model typed the first draft of this post.</p>
<p>What I don&#8217;t do is ask it for &#8220;a blog on a trending topic.&#8221; That&#8217;s where slop comes from: the machine supplies the thinking and a person supplies the publish button. I do it the other way round. I bring the idea. I give my opinions, my own inputs, what I&#8217;d recommend, and a clear path for the argument to take. Then I work on what comes back. I ask for it again, more than once. I add views it missed. I delete the stereotypes (there are always stereotypes).</p>
<p>That&#8217;s closer to working with a ghostwriter or a patient editor than to pressing a button, and writers used both long before 2022. It&#8217;s the same idea we follow in code, where <a href="https://www.macronimous.com/blog/controlled-ai-coding/">the AI works inside limits a person sets</a>.</p>
<p>Google, for what it&#8217;s worth, isn&#8217;t asking anyone to pretend either. Its <a href="https://developers.google.com/search/docs/fundamentals/using-gen-ai-content" target="_blank" rel="noopener noreferrer">guidance on generative AI content</a> cares about accuracy, quality and relevance, and suggests telling readers how a piece was made. So I&#8217;m telling you.</p>
<h2>Where the slop sneaks back in</h2>
<p>Here&#8217;s the part that&#8217;s easy to miss. The ideas are yours, so you assume the post is yours. But readers judge the sentences first, long before they reach the idea, and the sentences are still the model&#8217;s.</p>
<p>Two things happen in that gap.</p>
<p>The model smooths your opinions. You say &#8220;this is a mistake.&#8221; The draft says &#8220;this may not be ideal for every business.&#8221; Same topic, no spine. It&#8217;s worth checking that every opinion survived at full strength.</p>
<p>And the model keeps its own mold. Your input gets poured into a warm-up opening, even-sized sections and a summary at the end. You can delete a hundred clichés and still be left with the biggest one, which is the shape.</p>
<p>The fix is smaller than it sounds. If you only have time to hand-write three things, make them the opening, the ending, and the lines where you take a side. Those carry most of the voice. The middle can stay drafted and, as far as I can tell, nobody notices.</p>
<h2>What works on every post</h2>
<p>You can&#8217;t tell a story from your own history every time. We certainly can&#8217;t, not across 372 posts. These don&#8217;t need one.</p>
<ul class="mac-checklist">
<li>The post takes a side. &#8220;Most agencies do X, and we think that&#8217;s a mistake&#8221; needs no anecdote, only nerve.</li>
<li>Each section has one detail only a doer would know. Generic: &#8220;caching can cause issues on stores.&#8221; Practitioner: &#8220;on older WooCommerce stores, a full-page cache shows every visitor the same cart count unless the cart fragments call runs, and that call can&#8217;t be cached, so it ends up the slowest thing on the page.&#8221;</li>
<li>The depth is uneven on purpose. Skip what the reader already knows and spend 400 words on the one thing that matters.</li>
<li>There&#8217;s one honest &#8220;it depends,&#8221; in the one place where it really does, and it says what it depends on.</li>
<li>The warm-up opening and the summary ending are gone. This costs nothing.</li>
</ul>
<p>When you do have history, use it, because it&#8217;s the one thing nobody can copy. In November 2016 I wrote about <a href="https://www.macronimous.com/blog/lost-your-website-access-credentials-here-is-how-to-retrieve-them/">a client whose designer left with every server password</a>, and I put four exclamation marks in one sentence, followed by &#8220;Wow!&#8221; It isn&#8217;t elegant – but somebody was clearly upset when they typed it, and models don&#8217;t get upset.</p>
<h2>The test I use now</h2>
<p>If you came here asking &#8220;why is my writing flagged as AI,&#8221; I&#8217;d gently suggest that&#8217;s the wrong thing to fix. I&#8217;ve stopped asking &#8220;does this look human?&#8221; as well. The answer changes with the fashion.</p>
<div class="mac-key-point">
<p>The question I ask before publishing is: could anyone else have written this?</p>
</div>
<p>If the answer is yes, it&#8217;s slop, whether a person or a model typed it (content farms were turning it out by hand long before 2022). If the answer is no, the post proves itself, dashes and all. That matters for search as well, and it&#8217;s a big part of <a href="https://www.macronimous.com/blog/seo-strategy-2026/">how we&#8217;re approaching SEO in 2026</a>.</p>
<p>(There are three dashes in this post, by the way. One of them sits in a title from 2013. I&#8217;m keeping all three.)</p>
<p>If you&#8217;ve found a better test, write it in the comments and I&#8217;ll add it here with credit.</p>
<div class="mac-cta-box">
<h3>Content that reads like your business wrote it</h3>
<p>We plan and write <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">SEO</a> content with a practitioner in the loop at every step, so your pages say something only your company could say.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/digital-marketing/outsource-seo-services/">See how we handle SEO content</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/why-is-my-writing-flagged-as-ai/">Why Is My Writing Flagged as AI? We Used Dashes Before the Machines Did</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/why-is-my-writing-flagged-as-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Is Headless WordPress Worth It? The Pros, Cons, and Hidden Costs</title>
		<link>https://www.macronimous.com/blog/is-headless-wordpress-worth-it/</link>
					<comments>https://www.macronimous.com/blog/is-headless-wordpress-worth-it/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Tue, 29 Sep 2026 12:13:52 +0000</pubDate>
				<category><![CDATA[Headless CMS]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[Headless WordPress]]></category>
		<category><![CDATA[next.js]]></category>
		<category><![CDATA[Wordpress development]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5395</guid>

					<description><![CDATA[<p>Headless WordPress is the wrong choice for most business websites. A well-cached traditional WordPress site already loads fast, and while headless does improve security, that&#8217;s a bonus rather than a reason to rebuild. What you do get is a second codebase, a WordPress backend working as an API server, plugins that stop working, and an [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/is-headless-wordpress-worth-it/">Is Headless WordPress Worth It? The Pros, Cons, and Hidden Costs</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Is-Headless-WordPress-Worth-It.jpg"><img decoding="async" width="1733" height="907" class="aligncenter size-full wp-image-5408" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Is-Headless-WordPress-Worth-It.jpg" alt="Is Headless WordPress Worth It" /></a>
<div class="mac-direct-answer">
<p><strong>Headless <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a></strong> is the wrong choice for most business websites. A well-cached traditional WordPress site already loads fast, and while headless does improve security, that&#8217;s a bonus rather than a reason to rebuild. What you do get is a second codebase, a WordPress backend working as an API server, plugins that stop working, and an editing team that needs a developer for simple changes. It pays off mainly for multi-channel publishing and in-house JavaScript teams.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#faster">Is headless WordPress faster than regular WordPress?</a></li>
<li><a href="#security">Headless WordPress security: better, but not the main reason</a></li>
<li><a href="#backend">Headless WordPress backend overhead on large sites</a></li>
<li><a href="#plugins">Which WordPress plugins stop working when you go headless</a></li>
<li><a href="#editors">How headless WordPress affects your content editors</a></li>
<li><a href="#cost">The hidden costs of headless WordPress</a></li>
<li><a href="#when-headless">When headless WordPress is the right choice</a></li>
<li><a href="#faq">Headless WordPress FAQs</a></li>
<li><a href="#verdict">So, is headless WordPress worth it?</a></li>
</ul>
</div>
<p>Last October I published <a href="https://www.macronimous.com/blog/wordpress-to-headless/">a post on moving from WordPress to headless</a> about the promised blazing-fast performance. I still like the architecture. I&#8217;d write that post differently today, though. It told you what headless does well, which is the half every agency tells you (ours included, if I&#8217;m honest), and it skipped the half you only learn after the invoice arrives.</p>
<p>So this is the other half. It&#8217;s for business owners and marketing managers who&#8217;ve been told headless is the upgrade, and who&#8217;d like to know what they&#8217;re signing up for before somebody quotes them for a Next.js rebuild.</p>
<h2 id="faster">Is headless WordPress faster than regular WordPress?</h2>
<p>Sometimes. But the site people usually compare it against is a <em>slow</em> WordPress site, and that&#8217;s an unfair fight.</p>
<p>A traditional WordPress page behind full-page caching is already a static HTML file by the time a visitor asks for it. Put a CDN (content delivery network) in front, or use Cloudflare&#8217;s APO (Automatic Platform Optimization) for WordPress, and that file gets served from a data center near the visitor. That&#8217;s the same trick a headless Next.js front end uses. On first load, the gap between the two is often small.</p>
<p>Where headless can actually lose is after the page appears. The browser still has to download and run the JavaScript bundle before buttons and menus respond, and Google measures that responsiveness with INP (Interaction to Next Paint), one of the <a href="https://web.dev/articles/vitals" target="_blank" rel="noopener noreferrer">Core Web Vitals</a>. A marketing page carrying a heavy React bundle can pass the loading test and fail the interaction test.</p>
<p>Then there&#8217;s freshness. Pre-built pages go stale, so frameworks regenerate them on a timer or when WordPress sends a webhook (Next.js calls this <a href="https://nextjs.org/docs/app/guides/incremental-static-regeneration" target="_blank" rel="noopener noreferrer">incremental static regeneration</a>). When that webhook fails quietly, your editor updates a price, clicks Publish, and the live site keeps showing the old price until someone spots it. Nothing&#8217;s broken, exactly. It&#8217;s just one more moving part that didn&#8217;t exist before.</p>
<h2 id="security">Headless WordPress security: better, but not the main reason</h2>
<p>Let&#8217;s give headless its due here. It does improve security, and I won&#8217;t pretend otherwise.</p>
<p>Visitors never talk to WordPress directly. The public pages are pre-built files or come from a separate front end, so a flaw in a plugin that prints something into your pages isn&#8217;t exposed to the public the same way. The login page and the API can sit on a private subdomain behind IP rules. And when a bot floods the site, it hits the front end and the CDN, not PHP and MySQL.</p>
<p>But WordPress still runs somewhere, usually on the same hosting it ran on before. Its <a href="https://developer.wordpress.org/rest-api/" target="_blank" rel="noopener noreferrer">REST API</a> (the data feed your new front end reads from) is public by default, and out of the box it will list your authors&#8217; usernames at <code>/wp-json/wp/v2/users</code> to anyone who asks. Every plugin on that backend still needs patching. And now you also have a Node.js host, whether that&#8217;s Vercel, Netlify, or your own server, with its own packages, environment variables, and deploy keys to look after. The security gain is real, but only if someone locks down the backend and keeps patching both halves for as long as the site lives.</p>
<p>The team at Tars wrote <a href="https://hellotars.com/blog/engineering/tars-blog-from-wordpress-to-webflow-and-mdx" target="_blank" rel="noopener noreferrer">an unusually candid account</a> of running their blog on headless WordPress with Next.js. The front end did stop a struggling WordPress server from taking the whole site down, and that&#8217;s a real win. But they found the backend was just as fragile as before; they&#8217;d only moved the place where they had to deal with it. They ended up adding timeouts and teaching their cache to tell &#8220;this post was deleted&#8221; apart from &#8220;the API didn&#8217;t answer.&#8221; In May 2026 they moved their marketing content off WordPress altogether.</p>
<div class="mac-key-point">
<p>Headless doesn&#8217;t take WordPress out of your stack. It puts a second stack in front of it.</p>
</div>
<p>Here&#8217;s the thing, though. Most of that protection is available to a traditional WordPress site too. A WAF (web application firewall) such as Cloudflare&#8217;s, an IP-restricted login, fewer plugins, and updates that actually get applied will close most of the same doors, for a small fraction of what a rebuild costs. So count better security as a bonus that comes with headless. If it&#8217;s the main reason on your list, harden the site you have first. The real reasons to go headless are in the checklist further down.</p>
<h2 id="backend">Headless WordPress backend overhead on large sites</h2>
<p>This is the part that bites large sites hardest, and I&#8217;ll admit it wasn&#8217;t in the first draft of this post.</p>
<p>On a traditional WordPress site, a page cache does most of the work. The first visitor makes WordPress build the page, and the next ten thousand get a saved copy. PHP and MySQL barely wake up.</p>
<p>Headless changes who&#8217;s asking. Your front end now calls WordPress for data when it builds pages, when it regenerates them, when an editor previews a draft, and on any page rendered at request time. Most page-cache plugins don&#8217;t cache REST API responses by default. And a single GraphQL query through <a href="https://www.wpgraphql.com/" target="_blank" rel="noopener noreferrer">WPGraphQL</a> can pull posts, authors, categories, and custom fields in one nested request that&#8217;s <em>expensive</em> to run. So the server you thought you&#8217;d demoted to &#8220;just the admin&#8221; has become an API server, and it needs to be sized like one.</p>
<p>At scale, it compounds. A site with tens of thousands of product or article pages can&#8217;t rebuild everything on every publish, so it leans on on-demand regeneration, which means live traffic keeps reaching WordPress anyway. Then come the add-ons: object caching with Redis, a cache for API responses (WPGraphQL Smart Cache exists for exactly this reason), purge rules that have to agree across WordPress, the CDN, and the front end, a staging copy of both halves, and monitoring for an API that isn&#8217;t allowed to time out. Media usually still lives in WordPress too, so image traffic and storage don&#8217;t move either.</p>
<p>None of this is exotic for an enterprise team. But it&#8217;s a second hosting budget and a second operations job, and it belongs in the estimate before anyone signs.</p>
<h2 id="plugins">Which WordPress plugins stop working when you go headless</h2>
<p>The theme isn&#8217;t the only thing a headless front end replaces. Anything a plugin used to print into your pages is gone, because the pages don&#8217;t belong to WordPress anymore.</p>
<table class="styled-table">
<thead>
<tr>
<th>What you rely on</th>
<th>Traditional WordPress</th>
<th>Headless WordPress</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">SEO</a> meta tags from Rank Math or Yoast</td>
<td>The plugin prints them into every page.</td>
<td>Your developers fetch them through an API and render them. Rank Math offers a REST endpoint for this but has no built-in GraphQL support.</td>
</tr>
<tr>
<td>Canonical URLs, Open Graph URLs, and sitemaps</td>
<td>They&#8217;re correct by default.</td>
<td>They point at the WordPress domain unless someone rewrites them for the front-end domain.</td>
</tr>
<tr>
<td>Forms such as Gravity Forms or WPForms</td>
<td>You drop in a shortcode and you&#8217;re done.</td>
<td>Each form gets rebuilt as a front-end component that posts to an API.</td>
</tr>
<tr>
<td>Post preview</td>
<td>The editor clicks Preview.</td>
<td>The front end needs a preview route and authentication before Preview works at all.</td>
</tr>
<tr>
<td>Page builders such as Elementor or Divi</td>
<td>Editors build layouts visually.</td>
<td>They&#8217;re mostly unusable, because layouts now live in code.</td>
</tr>
<tr>
<td>WooCommerce cart and checkout</td>
<td>Payment and shipping plugins just work.</td>
<td>The cart and checkout become a custom build on top of the WooCommerce Store API.</td>
</tr>
</tbody>
</table>
<p>None of this is impossible. We&#8217;ve written about <a href="https://www.macronimous.com/blog/javascript-seo-techniques-canonicalization-and-schema-markup/">JavaScript SEO, canonicals, and schema</a> before, and every row in that table can be solved. But you&#8217;d be paying, by the hour, to re-solve problems WordPress had already solved for you.</p>
<h2 id="editors">How headless WordPress affects your content editors</h2>
<p>Developers tend to love headless builds. Editors tend to put up with them.</p>
<p>On a regular WordPress site, a marketing manager can spin up a campaign landing page on Tuesday morning and share it by lunch. On a headless site, a new layout usually means a new component, and a new component means a ticket, a developer, a code review, and a deploy. Preview may or may not show what the live page will look like. The block editor might still be there, but the front end only renders the blocks someone has written code for (anything else just doesn&#8217;t show up).</p>
<p>That&#8217;s fine for a product team that ships weekly anyway. For a five-person marketing team that used to be self-sufficient, it&#8217;s a real loss, and it rarely comes up in the sales call.</p>
<h2 id="cost">The hidden costs of headless WordPress</h2>
<p>You&#8217;re paying for two hosts. You&#8217;re paying for two sets of updates. You&#8217;re paying a React developer for jobs a content editor used to handle alone. And you&#8217;re paying, sooner or later, for someone to work out why the build failed on a Friday afternoon.</p>
<p>The build itself is the part everyone quotes, and it&#8217;s rarely the part that hurts, because the ongoing work is where headless quietly gets expensive: every WordPress update now has to be tested against an API your front end depends on, and every new plugin has to be checked for whether it even exposes its data to that API, so a job that used to be &#8220;update and eyeball the homepage&#8221; becomes a two-person, two-repository exercise that somebody has to schedule, and on most small teams that somebody doesn&#8217;t exist.</p>
<p>It&#8217;s the same kind of <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">hidden technical debt</a> that builds up in overgrown WordPress sites. It&#8217;s just split across two codebases now, and two invoices.</p>
<h2 id="when-headless">When headless WordPress is the right choice</h2>
<p>I realize an agency with a headless service page is an odd source for this post. Fair enough. We do build these, and for some projects nothing else makes sense. Headless is usually worth it when most of these are true:</p>
<ul class="mac-checklist">
<li>The same content has to feed a website plus a mobile app, a kiosk, or another product.</li>
<li>You have JavaScript developers on the team, or you&#8217;ll pay for them every month, not just during the build.</li>
<li>Your editors are happy with structured, form-style editing and don&#8217;t need a visual page builder.</li>
<li>The site has app-like features, such as logged-in dashboards or heavy interactivity, that a WordPress theme would fight.</li>
<li>You&#8217;ve already tried caching, a CDN, a leaner theme, and fewer plugins, and the site still isn&#8217;t fast enough.</li>
<li>You&#8217;ve budgeted to run and scale the WordPress backend as an API server, not just to build the front end.</li>
<li>Nobody on the project is choosing headless mainly because it sounds modern.</li>
</ul>
<p>If you ticked two or fewer, fix the WordPress site you already have. It&#8217;s cheaper, and it&#8217;ll probably be faster than you expect. For what it&#8217;s worth, this blog is a plain WordPress install (Classic Editor and all) on InMotion hosting.</p>
<h2 id="faq">Headless WordPress FAQs</h2>
<div class="faq-item">
<h3>Does going headless make a WordPress site faster?</h3>
<p>Not automatically. A traditional WordPress site with full-page caching and a CDN serves pre-built HTML, much like a headless front end does. Headless can even be slower to respond to clicks if the JavaScript bundle is heavy, which shows up in the INP (Interaction to Next Paint) metric.</p>
</div>
<div class="faq-item">
<h3>Is headless WordPress more secure?</h3>
<p>Yes, somewhat. Visitors never reach WordPress directly, and the login page and API can be hidden on a private subdomain. But WordPress still runs behind the front end and needs patching, and a well-hardened traditional site gets much of the same protection. It&#8217;s a real benefit, but a weak reason on its own to go headless.</p>
</div>
<div class="faq-item">
<h3>Does Rank Math work with headless WordPress?</h3>
<p>Partly. Rank Math&#8217;s Headless CMS Support setting exposes a REST endpoint that returns a page&#8217;s meta tags, and your front end has to fetch and render them. Rank Math doesn&#8217;t support GraphQL out of the box, so WPGraphQL-based builds need extra integration work.</p>
</div>
<div class="faq-item">
<h3>Does headless WordPress reduce the load on the WordPress server?</h3>
<p>Usually not. The front end calls WordPress through its REST or GraphQL API whenever it builds, regenerates, or previews pages, and those API responses often aren&#8217;t cached by default. Large headless sites typically need object caching, API response caching, and a backend sized as an API server.</p>
</div>
<h2 id="verdict">So, is headless WordPress worth it?</h2>
<p>Headless WordPress is a good tool that gets sold to the wrong customers. If you run a headless site and think I&#8217;ve got part of this wrong, tell me in the comments. I&#8217;d like to know which parts held up for you.</p>
<div class="mac-cta-box">
<h3>Not sure headless is right for your site?</h3>
<p>We build headless WordPress front ends, and we&#8217;ll also tell you plainly when your site just needs caching and a cleanup instead.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/cms-development/wordpress-to-headless/">Get a Headless Fit Check</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/is-headless-wordpress-worth-it/">Is Headless WordPress Worth It? The Pros, Cons, and Hidden Costs</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/is-headless-wordpress-worth-it/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</title>
		<link>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/</link>
					<comments>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Sun, 20 Sep 2026 12:18:41 +0000</pubDate>
				<category><![CDATA[CMS]]></category>
		<category><![CDATA[Content Management Systems]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Site Speed]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<category><![CDATA[Auditra]]></category>
		<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[MCP Server]]></category>
		<category><![CDATA[WordPress performance]]></category>
		<category><![CDATA[WP-Cron]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5324</guid>

					<description><![CDATA[<p>A slow WordPress admin is usually blamed on plugin count, and plugin count is usually only part of it. On our own site the real chain was a six-process PHP-FPM pool on shared hosting, 2,800 daily wp-cron hits, 6,700 daily bot 404s, and two plugins waiting seven seconds on dead remote servers. A read-only WordPress [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/">Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Our-WordPress-Admin-Took-13-Seconds-a-Page.jpg"><img decoding="async" width="1672" height="940" class="aligncenter size-full wp-image-5355" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Our-WordPress-Admin-Took-13-Seconds-a-Page.jpg" alt="Slow WordPress admin Troubleshooting with MCP" /></a>
<div class="mac-direct-answer">
<strong>A slow <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> admin</strong> is usually blamed on plugin count, and plugin count is usually only part of it. On our own site the real chain was a six-process PHP-FPM pool on shared hosting, 2,800 daily wp-cron hits, 6,700 daily bot 404s, and two plugins waiting seven seconds on dead remote servers. A read-only WordPress <a href="https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/">MCP</a> server found the first problem in a minute and the last one needed a request profiler. This is what each tool could and couldn&#8217;t see.</div>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#what-happened">What happened on a Tuesday</a></li>
<li><a href="#where-to-look">Why is the admin slow when the front end is fine? Where to look, in order</a></li>
<li><a href="#autoload">The 12 MB autoload: a real problem, and the wrong one</a></li>
<li><a href="#where-the-audit-stops">Where the audit stops</a></li>
<li><a href="#php-fpm-workers">Six PHP-FPM workers: the max_children limit on shared hosting</a></li>
<li><a href="#wp-cron-and-bots">WP-Cron and bot 404s were eating the pool</a></li>
<li><a href="#last-seven-seconds">The last seven seconds</a></li>
<li><a href="#what-we-changed">What we changed</a></li>
<li><a href="#what-audit-is-for">What a read-only WordPress MCP server is actually for</a></li>
</ul>
</div>
<h2 id="what-happened">What happened on a Tuesday</h2>
<p>On the 8th of September, macronimous.com&#8217;s admin went from sluggish to unusable. Opening Pages took long enough to make coffee. By the Wednesday, visitors were getting Cloudflare&#8217;s <a href="https://developers.cloudflare.com/support/troubleshooting/http-status-codes/cloudflare-5xx-errors/error-524/" target="_blank" rel="noopener noreferrer">524 timeout page</a> on the front end, which means Cloudflare reached our server, asked for the page, and gave up waiting after a hundred seconds. The site has been on the same WordPress install since 2015, it has 54 active plugins (we know), and it shares an InMotion account with our blog, which is a second, separate install with 33 plugins of its own.</p>
<p>We&#8217;d published <a href="https://wordpress.org/plugins/auditra/" target="_blank" rel="noopener noreferrer">Auditra</a> three weeks earlier, a free, read-only MCP server for WordPress that lets an <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> client read a site&#8217;s plugin inventory, autoload, cron schedule, database tables and known vulnerabilities. So the obvious move was to point it at our own site and see how far it got. This post is the honest version of that: what it found, where it stopped, and what it took to get the rest of the way.</p>
<p>We&#8217;ll warn you now, it runs long, because the interesting part is the sequence of wrong turns and not the final fix.</p>
<h2 id="where-to-look">Why is the admin slow when the front end is fine? Where to look, in order</h2>
<p>If you&#8217;re here because your own admin is crawling and you&#8217;d rather skip the story, this is the order we&#8217;d check now, having done it the long way.</p>
<ol>
<li><strong>cPanel ? Resource Usage.</strong> If CPU and memory are low, faults are zero, and Entry Processes reads 0 while the site is timing out, your PHP workers are running outside your account and you can&#8217;t see them. That&#8217;s a hosting question, not a plugin question.</li>
<li><strong>Bypass Cloudflare.</strong> Point the domain at the origin IP in your hosts file and hit the login page with <code>curl</code>. If it hangs direct, Cloudflare is cleared.</li>
<li><strong>Hit a bare <code>phpinfo.php</code>.</strong> If a one-line PHP file hangs, WordPress is cleared too.</li>
<li><strong>Ask the host two things:</strong> the PHP-FPM <code>max_children</code> for your pool, and a one-day count of requests to <code>wp-cron.php</code>, 404s, and top user agents.</li>
<li><strong>Read the autoload, cron list, and plugin inventory</strong> with a read-only audit (Auditra, WP-CLI, or by hand). Fix what&#8217;s obviously wrong, but don&#8217;t assume it&#8217;s the cause.</li>
<li><strong>Install Query Monitor</strong>, open an admin page, and read the HTTP API Calls tab before anything else. Dead remote calls hide there.</li>
</ol>
<p>Now the long way.</p>
<h2 id="autoload">The 12 MB autoload: a real problem, and the wrong one</h2>
<p>The first call was <code>analyze_autoload</code>. Autoloaded options are the rows in <code>wp_options</code> that WordPress pulls out of the database on every single request, admin or not, before a plugin has done anything. A healthy site keeps that under a megabyte. Ours was 12.7 MB across 989 options, and 98% of it was unattributed, which is the tool&#8217;s way of saying no active plugin claimed ownership of those rows.</p>
<p>The largest entries were all <code>_transient_string-locator-search-files-N</code>, numbered past 200, at 77 to 90 KB each. String Locator is a search-and-replace plugin we&#8217;d used and then deactivated, and it never cleaned up after itself. Every one of those transients had no expiry, and every one of them was autoloaded. We ran one <code>DELETE</code> in phpMyAdmin (206 rows, 0.08 seconds), called <code>analyze_autoload</code> again, and watched the number drop to 730 KB.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5346" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-autoload-finding-12mb.png" alt="The audit result: 12.7 MB autoload, almost all of it leftover String Locator search transients, and the one-line fix" width="1400" height="852" /><figcaption class="wp-caption-text">The first result, one minute in. Correct finding, wrong diagnosis.</figcaption></figure>
<p>The first response from the model that read the audit opened with &#8220;Found it.&#8221; We&#8217;ve left that line in because it was wrong, and because it&#8217;s the most useful wrong answer in the whole story. A 12 MB autoload is a genuine defect that costs every request on the site, and fixing it changed nothing we could feel. The admin was exactly as slow afterwards. So the finding was correct and the diagnosis was not, and that distinction is the whole point of a tool that reports facts instead of verdicts.</p>
<h2 id="where-the-audit-stops">Where the audit stops</h2>
<p>Auditra reads state. It can&#8217;t time a request, and it can&#8217;t see anything below WordPress. So from here the work moved to things it has no access to: cPanel&#8217;s resource graphs, a shell, and the hosting company.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5350" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-cannot-time-requests-next-steps.png" alt="Where the audit stops: it cannot time a request, so the next steps move to cPanel and cron" width="1400" height="647" /><figcaption class="wp-caption-text">The hand-off point. A read-only audit reads state; timing a request is a different tool.</figcaption></figure>
<p>The cPanel numbers made no sense at first. CPU sat at 9 to 15% of the limit, memory under 250 MB of 4 GB, zero faults on every hourly row, and Entry Processes (the count of web requests being handled) at zero while the site was timing out. At one point CPU read 215% with Entry Processes still at zero and six processes total, which is not what a busy website looks like. A <code>ps aux</code> from the cPanel terminal during a hang showed nothing running under our user at all.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5348" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/cpanel-current-usage-215-cpu-zero-entry-processes.png" alt="cPanel Current Usage during the hang: 215% CPU, zero Entry Processes, six processes, no faults" width="1400" height="648" /><figcaption class="wp-caption-text">cPanel during a hang. Two cores busy, zero web requests being handled, six processes.</figcaption></figure>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5349" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/reading-cpanel-numbers-workers-outside-account.png" alt="Reading the cPanel numbers: CPU busy with zero entry processes means the work is happening outside the account" width="1400" height="637" /><figcaption class="wp-caption-text">The reading at the time: the work is happening somewhere the account can&#8217;t see. Right instinct, wrong layer.</figcaption></figure>
<p>To rule out Cloudflare we edited the hosts file on a Mac to point the domain straight at the InMotion IP and ran <code>curl</code> against the origin. The home page came back in 1.4 seconds (a cached file served by WP Fastest Cache from disk, no PHP involved). The login page hung indefinitely. So did <code>phpinfo.php</code>, a one-line file with no WordPress in it. That was the moment the site stopped being the suspect. Nothing in WordPress can make a bare <code>phpinfo()</code> hang.</p>
<p>We read all of that, at the time, as &#8220;requests aren&#8217;t reaching PHP.&#8221; That turned out to be the wrong layer too, and we&#8217;ll own it: the requests were inside PHP, in a place we couldn&#8217;t see.</p>
<h2 id="php-fpm-workers">Six PHP-FPM workers: the max_children limit on shared hosting</h2>
<p>The InMotion engineer (a patient man, by the third hour) found it: the PHP-FPM pool for the account was hitting its <code>max_children</code> limit, and that limit was six. Six PHP processes for a 54-plugin admin, a 33-plugin blog, all their cron jobs, and every bot on the internet. And the number is fixed per plan on shared hosting; it can&#8217;t be raised, and there&#8217;s no slow log to tell you what&#8217;s holding the slots.</p>
<p>In hindsight every odd number fit. The pool runs as a server user outside the cPanel account, which is why <code>ps</code> showed nothing and Entry Processes read zero. The six processes and the 215% CPU on Tuesday were the six workers, all busy. And the front page kept loading because a cached HTML file never asks the pool for anything.</p>
<div class="mac-key-point">
<p>On shared hosting, the number that decides whether your admin works is one you can&#8217;t see in WordPress, can&#8217;t see in cPanel, and can&#8217;t change. Ask your host what it is before you trust any other diagnosis.</p>
</div>
<h2 id="wp-cron-and-bots">WP-Cron and bot 404s were eating the pool</h2>
<p>What was filling six slots all day? The same engineer pulled a day of access logs, and this is where the audit&#8217;s cron finding from the first hour turned out to matter after all.</p>
<table class="styled-table">
<thead>
<tr>
<th>Request type (one day)</th>
<th>Count</th>
<th>Cost</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>POST /blog/wp-cron.php</code></td>
<td>2,086</td>
<td>Full WordPress load each, one worker held</td>
</tr>
<tr>
<td><code>POST /wp-cron.php</code> (main site)</td>
<td>701</td>
<td>Same</td>
</tr>
<tr>
<td>404s under <code>/blog/</code></td>
<td>6,758</td>
<td>Uncached, full WordPress load each</td>
</tr>
<tr>
<td>WP Fastest Cache preload bot</td>
<td>823</td>
<td>Our own plugin, hitting our own site</td>
</tr>
<tr>
<td>AwarioBot + a webmeup scraper</td>
<td>1,400</td>
<td>Nothing we wanted</td>
</tr>
</tbody>
</table>
<p>WordPress has no clock. Its scheduled jobs run when a visitor&#8217;s page load notices one is due and spawns an extra request to <code>wp-cron.php</code>. With bots hitting the site a few thousand times a day, that&#8217;s a few thousand cron spawns, each one taking a worker for a full WordPress boot. Auditra had listed 65 cron events on the main site, one of them (a management connector we&#8217;d installed and never used) firing every minute. We&#8217;d noted it as &#8220;secondary&#8221; on day one. It wasn&#8217;t secondary. It was a sixth of the pool, permanently.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5347" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/audit-secondary-findings-cron-tables.png" alt="Secondary findings from the same audit: 65 cron events, heavy security log tables, no object cache" width="1400" height="452" /><figcaption class="wp-caption-text">The &#8220;secondary&#8221; findings from day one. The cron line turned out to be the main event.</figcaption></figure>
<p>The 404s were ordinary background noise, scanners probing for install.php and fake plugin paths, no single source worth blocking. The problem was only that each one cost a worker, because a 404 page is never cached.</p>
<h2 id="last-seven-seconds">The last seven seconds</h2>
<p>After the cron and bot traffic was dealt with (details below), the admin was still slow, and this is where a read-only audit has nothing left to offer. You need something that times a live request. We installed <a href="https://wordpress.org/plugins/query-monitor/" target="_blank" rel="noopener noreferrer">Query Monitor</a>, opened the Pages list, and read the panel: 13.2 seconds page generation, 569 database queries, and an HTTP API tab that told the rest of the story in four lines.</p>
<figure class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-5351" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/query-monitor-timeline-569-queries.png" alt="Query Monitor timeline for the Pages list: 569 database queries, two PHP errors, 13.2 seconds page generation" width="1400" height="414" /><figcaption class="wp-caption-text">Query Monitor on the Pages list: 569 queries, 13.2 seconds, and an HTTP API tab worth more than all of them.</figcaption></figure>
<p>Simple Job Board, which ran our careers page, was calling its vendor&#8217;s add-on marketplace on every admin page load to check for updates. That server was stuck in a redirect loop, so each call ran 2.8 seconds before giving up, and it made the call twice. Five and a half seconds per admin page, holding a worker the whole time, for a marketplace we&#8217;d never bought anything from. CrawlWP, an indexing plugin, was trying to refresh a Yandex Webmaster token twice per page and getting a 400 each time, another 1.2 seconds, for a search engine none of our clients use.</p>
<p>We deactivated both. Page generation went from 13.2 seconds to 1.37.</p>
<p>Neither plugin was flagged by anything. Both were up to date, both were actively maintained, both had clean vulnerability records. An inventory tool can&#8217;t see that a plugin&#8217;s phone-home endpoint died last month. Only a profiler watching the request can, and only when you happen to look.</p>
<h2 id="what-we-changed">What we changed</h2>
<p>For anyone in the same situation, this is the list, in the order we did it. Most of it is in the <a href="https://developer.wordpress.org/advanced-administration/wordpress/wp-config/" target="_blank" rel="noopener noreferrer">wp-config reference</a> and Cloudflare&#8217;s <a href="https://developers.cloudflare.com/cache/how-to/cache-rules/" target="_blank" rel="noopener noreferrer">cache rules docs</a> if you want the full detail.</p>
<ul class="mac-checklist">
<li>Deleted 206 orphaned String Locator transients (12.7 MB autoload to 730 KB)</li>
<li>Removed the every-minute management connector, and confirmed via <code>analyze_cron</code> that it left no orphaned events</li>
<li><code>define('DISABLE_WP_CRON', true);</code> on both installs, with two cPanel server crons at 5 and 15 minutes replacing 2,800 daily visitor-triggered hits with about 380</li>
<li>Cloudflare cache rule: 404 responses under <code>/blog/</code> cached at the edge for an hour, so repeat scans never reach PHP</li>
<li>Cloudflare WAF rules blocking the two scrapers by user agent, and the usual scanner paths (<code>install.php</code>, <code>xmlrpc.php</code>, <code>.env</code>)</li>
<li>A physical <code>robots.txt</code> for the blog, because WordPress only serves its virtual one at the domain root</li>
<li>WP Fastest Cache preload throttled to 4 pages a minute</li>
<li>13 inactive plugins deleted, 8 redundant active ones removed, 2 deactivated (down to about 42 active)</li>
<li>Simple Job Board and CrawlWP deactivated; the careers page became a paragraph of text</li>
</ul>
<p>Two things we didn&#8217;t fix, for the record. The theme depends on Titan Framework, which has an unfixed medium-severity CVE from 2021 and has been closed on wordpress.org for years; replacing it is a theme rework, not a Tuesday job. And the blog&#8217;s 33 plugins haven&#8217;t been audited yet. If you&#8217;ve read our post on <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">technical debt in old WordPress installs</a>, you already know how this goes: you never finish, you just get to the point where the site stops fighting you.</p>
<h2 id="what-audit-is-for">What a read-only WordPress MCP server is actually for</h2>
<p>We built Auditra to be read-only on purpose, and we wrote up why in the <a href="https://www.macronimous.com/blog/wordpress-plugin-security-visibility/">plugin security post</a>: no write calls, no scores, facts with thresholds next to them, and the reasoning left to whoever (or whatever) reads the output. This week was the first real test of that split on a site we actually cared about, and here&#8217;s our verdict.</p>
<p>It did the first hour and the inventory. Autoload, cron, table sizes, the plugin list with vulnerability flags and content usage, all in a few calls, with none of the clicking through fifty settings screens that a plugin cleanup usually means. And it did the verification: after every change, one call confirmed the cron flag was set, the connector was gone, the server cron had fired. That&#8217;s the part people underrate. Knowing a fix took is worth as much as the fix.</p>
<p>It could not diagnose a live hang, and we don&#8217;t think a read-only tool should pretend to. That took cPanel graphs, a hosts-file bypass, the host&#8217;s view of a pool we couldn&#8217;t see, and a profiler timing a real request. Four tools, none of them the audit. If we&#8217;d sold Auditra as &#8220;finds your performance problem,&#8221; it would have failed this week, publicly, on our own site.</p>
<p>So the pitch we&#8217;d make to another senior WordPress developer is smaller than the one we started with, and we think it&#8217;s the right one. Every site you inherit has a history you can&#8217;t see from the admin. A read-only audit reads that history in a minute, in a form an AI client can reason over, and tells you what to rule out first. Ruling out is where the hours go on a bad day. It won&#8217;t tell you the pool has six workers. But it&#8217;ll get you to the question a lot faster, and it&#8217;ll confirm you fixed what you think you fixed.</p>
<p>That&#8217;s what we found on our own site. We&#8217;d be curious what a ten-year-old install of yours turns up, and if Auditra tells you something you didn&#8217;t know, or misses something it should have caught, the comments are open and we&#8217;ll add it with credit.</p>
<div class="mac-cta-box">
<h3>Run the same audit on a site you maintain</h3>
<p>Auditra is free, GPL, and read-only. Install it, connect any MCP client, and read your own site&#8217;s autoload, cron, tables and plugin history in a few calls. If what comes back is more than you want to deal with, <a href="https://www.macronimous.com/services/cms-development/wordpress-maintenance-services/">we&#8217;ve been cleaning up WordPress installs since 2008</a>.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/free-tools/auditra/">Get Auditra and point it at your site</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/">Our WordPress Admin Took 13 Seconds a Page. Here&#8217;s the Audit.</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/wordpress-admin-slow-read-only-audit-case-study/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Should Your Website Block AI Bots? A Practical 2026 Answer</title>
		<link>https://www.macronimous.com/blog/should-you-block-ai-bots-website-seo/</link>
					<comments>https://www.macronimous.com/blog/should-you-block-ai-bots-website-seo/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Wed, 16 Sep 2026 06:03:24 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Technical SEO]]></category>
		<category><![CDATA[AEO]]></category>
		<category><![CDATA[AI crawlers]]></category>
		<category><![CDATA[Cloudflare]]></category>
		<category><![CDATA[robots.txt]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5358</guid>

					<description><![CDATA[<p>Blocking AI bots is the wrong question, because there are three kinds and they do different things. Search bots and agent bots send you traffic or citations, so allow them everywhere. Training bots give nothing back, so decide page by page: allow on marketing content you want AI to know about, block on anything you [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/should-you-block-ai-bots-website-seo/">Should Your Website Block AI Bots? A Practical 2026 Answer</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Should-Your-Website-Block-AI-Bots-A-Practical-2026-Answer.jpg"><img loading="lazy" decoding="async" width="1672" height="940" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/Should-Your-Website-Block-AI-Bots-A-Practical-2026-Answer.jpg" alt="Should Your Website Block AI Bots A Practical 2026 Answer" class="aligncenter size-full wp-image-5362" /></a>
<div class="mac-direct-answer">
<p><strong>Blocking <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> bots</strong> is the wrong question, because there are three kinds and they do different things. Search bots and agent bots send you traffic or citations, so allow them everywhere. Training bots give nothing back, so decide page by page: allow on marketing content you want AI to know about, block on anything you charge for. And in Cloudflare, use the new Disallow AI Training setting, not Block: since September 15, 2026, Block stops Googlebot too.</p>
</div>
<h2>The question a client asked me last month</h2>
<p>&#8220;Should we just block all the AI bots?&#8221; That&#8217;s the email, more or less, that landed from a client who runs a cremation arrangement service a few weeks ago. He&#8217;d read a headline about Cloudflare blocking AI crawlers, opened his dashboard, found a toggle that said Block AI Bots, and had his finger on it. He wanted to know if it was a good idea.</p>
<p>It isn&#8217;t, for him. And I suspect it isn&#8217;t for most of the people reading this, because if you run a business site, your content isn&#8217;t the product. It&#8217;s the bait. The whole point of the service page is that someone finds it, reads it, and calls you. If an AI assistant reads it and tells a prospect &#8220;these people do Magento migrations, here&#8217;s their site,&#8221; that&#8217;s the same outcome, just with a different middleman.</p>
<p>So no, I wouldn&#8217;t block everything. But I also wouldn&#8217;t allow everything, and the reason is that &#8220;AI bots&#8221; is three different things wearing one label.</p>
<h2>Three bots, not one</h2>
<p>Every big AI company now runs at least three crawlers, and they have different jobs. OpenAI is the clearest about it. <a href="https://developers.openai.com/api/docs/bots" target="_blank" rel="noopener noreferrer">OpenAI&#8217;s crawler documentation</a> lists GPTBot for training, OAI-SearchBot for ChatGPT&#8217;s search index, and ChatGPT-User for the moment a real person asks ChatGPT to open your page. Anthropic mirrors that with ClaudeBot, Claude-SearchBot, and Claude-User. Google does it the odd way round: one Googlebot fetches everything, and a separate robots.txt token called <a href="https://developers.google.com/crawling/docs/crawlers-fetchers/google-common-crawlers" target="_blank" rel="noopener noreferrer">Google-Extended</a> tells Google after the fact whether it may use what it fetched to train Gemini.</p>
<p>Cloudflare&#8217;s names for the three are the ones that stuck, so I&#8217;ll use them:</p>
<table class="styled-table">
<thead>
<tr>
<th>Type</th>
<th>What it does</th>
<th>Examples</th>
<th>What you get back</th>
</tr>
</thead>
<tbody>
<tr>
<td>Search</td>
<td>Indexes your pages so an engine can answer questions later</td>
<td>Googlebot, Bingbot, OAI-SearchBot, PerplexityBot</td>
<td>Referral traffic, citations in AI search</td>
</tr>
<tr>
<td>Agent</td>
<td>Fetches a page live because a person asked</td>
<td>ChatGPT-User, Claude-User, Google-Agent</td>
<td>A citation in the answer, sometimes a click</td>
</tr>
<tr>
<td>Training</td>
<td>Copies content to build the next model</td>
<td>GPTBot, ClaudeBot, CCBot, Google-Extended, Bytespider</td>
<td>Nothing direct. Maybe brand familiarity in a future model</td>
</tr>
</tbody>
</table>
<p>The trouble with a single Block AI Bots switch is that it treats a Perplexity fetch that&#8217;s about to cite you the same as a Bytespider crawl that&#8217;s copying your site for a model you&#8217;ll never hear of. Those aren&#8217;t the same decision.</p>
<h2>What each one is worth to a business site</h2>
<p>Search bots are easy. Block them and you vanish from search, and now &#8220;search&#8221; includes ChatGPT search, Perplexity, and Bing Copilot. OpenAI says plainly that a site which opts out of OAI-SearchBot won&#8217;t be shown in ChatGPT search answers. Allow these. I can&#8217;t think of a business reason not to.</p>
<p>Agent bots are the ones people don&#8217;t understand yet, and they&#8217;re the ones that matter most for what we&#8217;ve been calling <a href="https://www.macronimous.com/blog/answer-engine-optimization-aeo-optimizing-for-ai-powered-search/">answer engine optimization</a>. When someone types &#8220;reliable WooCommerce developers with <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">SEO</a> experience&#8221; into Claude or ChatGPT and the assistant goes and reads three agency sites before answering, that read is an agent fetch. If you&#8217;ve blocked it, you&#8217;re not in the answer. You don&#8217;t get a pageview in Google Analytics for it (well, sometimes you do, depending on the bot), but you get the thing a pageview was always a proxy for: a prospect hearing your name.</p>
<p>Training bots are the honest &#8220;it depends.&#8221; A training crawl gives you no click, no citation, and no attribution. What it might give you, and I&#8217;m guessing here because nobody outside the labs really knows, is that the next model has some idea who you are. For a small agency that&#8217;s arguably worth more than the content it takes. For a publisher whose articles <em>are</em> the product, it&#8217;s a straight loss, and that&#8217;s the audience Matthew Prince was writing for when he declared <a href="https://blog.cloudflare.com/content-independence-day-no-ai-crawl-without-compensation/" target="_blank" rel="noopener noreferrer">Content Independence Day</a> back in July 2025. His numbers were brutal: by Cloudflare&#8217;s count it&#8217;s roughly 750 times harder to get a click from OpenAI than from old-school Google, and 30,000 times harder from Anthropic. Fair enough, if you sell ads. Most of our clients don&#8217;t.</p>
<div class="mac-key-point">
<p>If your site exists to get you hired, being read by an AI assistant is the goal, not the threat. Block the bots that only take, and let in the ones that mention you.</p>
</div>
<h2>What I&#8217;d allow, and what I&#8217;d block</h2>
<p>Here&#8217;s the split I&#8217;d use for an agency, a SaaS product, a B2B service firm, or a local business. Publishers and paywalled sites are a different post.</p>
<p>Allow everywhere: every search crawler, every agent fetcher. No exceptions on public pages.</p>
<p>Allow on marketing content: training crawlers on your service pages, your blog, your case studies, your about page. This is the content you <em>want</em> a model to have absorbed when a prospect asks it a buying question next year. You wrote it to be found. Let it be found. The exceptions are in the next two paragraphs.</p>
<p>Block training on: anything you sell or gate. Pricing calculators, downloadable frameworks, client portals, proposal PDFs, course content, the internal playbook you accidentally left at /wp-content/uploads/. This is content with standalone value, and a training crawl is the only kind of visit that takes the value without leaving anything.</p>
<p>Block outright, whatever the category: crawlers that give nothing back and don&#8217;t declare themselves. Bytespider (ByteDance) is the usual offender. Unknown user agents hammering the same unchanged pages. Anything that ignores robots.txt, which, I should note, Cloudflare reported Perplexity doing with undeclared crawlers in August 2025. That one was a category problem, not a business decision.</p>
<h2>Where Cloudflare comes in, and what changed on September 15</h2>
<p>This is the part that&#8217;s been in the news, and it&#8217;s also the part I had to rewrite the night before publishing, because Cloudflare moved the goalposts on September 15. Bear with me for a slightly technical section.</p>
<p>On July 1, 2026, Cloudflare retired the single Block AI Bots toggle and replaced it with the three controls above: Search, Agent, and Training, each one you can set separately, on every plan including Free. The July announcement also warned that from September 15 a Training block would catch Googlebot, because Googlebot does search <em>and</em> training in one fetch. That warning is why half the SEO industry spent the summer nervous.</p>
<p>Then on September 15 Cloudflare published <a href="https://blog.cloudflare.com/accountable-mixed-use-ai-crawlers/" target="_blank" rel="noopener noreferrer">a follow-up</a> that mostly defuses it. There&#8217;s a fourth option for the Training control now, called Disallow AI Training. Pick it and Cloudflare writes the no-training preference into your robots.txt for you (Google-Extended, Applebot-Extended, and so on), keeps letting Googlebot, Applebot, and Bingbot crawl for search, and blocks the training-only crawlers from OpenAI, Anthropic, Meta, and Amazon at the edge. Google, Apple, and Microsoft signed up to what Cloudflare is calling the Accountable designation, which means they&#8217;ve promised that opting out of training won&#8217;t touch your rankings. One gap: Bing doesn&#8217;t read a no-training line in robots.txt yet (Microsoft says early 2027), so for Bing you still need the NOARCHIVE meta tag.</p>
<p>The trap didn&#8217;t go away, though. It moved. The plain Block setting on Training now does exactly what it says, which means a site owner who reads &#8220;Block&#8221; as &#8220;block AI&#8221; (a reasonable reading, and the one every headline since July has encouraged) and picks it because it sounds like the safe choice will find that Cloudflare takes them at their word and stops Googlebot, Applebot, and Bingbot at the edge, with a 403 that never touches robots.txt and never reaches the server, so the site looks fine from the inside while pages quietly drop out of the index, and the first anyone hears of it is a crawl-stats dip in Search Console three or four weeks later. The good news for the people who flipped the old toggle in 2025 and forgot: Cloudflare migrated that to Disallow AI Training, not Block, so Googlebot keeps coming. The migration also sets Agent to &#8220;Block on pages with ads,&#8221; which does nothing on a site without ads. If you never touched any of it, everything stays on Allow.</p>
<h2>Why blocking doesn&#8217;t protect you much anyway</h2>
<p>This next part is what the logs show, whether or not it sounds defeatist.</p>
<p>Robots.txt is a request, not a lock. The well-behaved crawlers honor it (OpenAI, Anthropic, Google, Bing all say they do, and as far as I can tell they mean it). The badly behaved ones don&#8217;t, and those are the ones you were worried about. A Cloudflare edge block is stronger because it&#8217;s enforced before the request reaches you, but it&#8217;s only as good as Cloudflare&#8217;s ability to identify the bot, and a crawler that spoofs a Chrome user agent from a residential proxy looks like a person. So you end up in the position where a block reliably keeps out the polite companies that would have cited you, and unreliably keeps out the ones that wouldn&#8217;t have. That&#8217;s a bad trade for a site whose content isn&#8217;t for sale.</p>
<p>Blocking is worth doing when the content has a price. As a gesture it costs you citations and protects nothing.</p>
<h2>What we do on our own sites</h2>
<p>Macronimous.com has sat behind Cloudflare since 2013 (I wrote about setting it up back then; it took under ten minutes and I stand by that). Our current policy is exactly the split above: Search and Agent allowed everywhere, Training allowed on the public marketing pages and blog, and blocked only on the handful of paths where we keep client deliverables and internal tooling. We&#8217;ve never turned on the legacy Block AI Bots toggle, so the September migration left everything on Allow, and I checked the dashboard on Tuesday to make sure. I&#8217;ll probably switch Training to Disallow AI Training once I&#8217;ve watched it for a couple of weeks on a test domain.</p>
<p>Over the last thirty days our logs show around 30 requests from agent-class bots against the blog. Jeffy on our team pulled that from Cloudflare&#8217;s AI Crawl Control; I haven&#8217;t verified it against the origin logs yet, so treat it as directional, and yes, thirty is a small number for a site our size. We&#8217;re also building a small internal tracker that asks the major assistants our own buying questions every week and records whether Macronimous comes up. Early, and I&#8217;ll write it up when the numbers mean something.</p>
<h2>The robots.txt I&#8217;d start with</h2>
<p>For a typical <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> business site, this is the baseline. Adjust the training block to your own gated paths.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"># Search: allow
User-agent: Googlebot
User-agent: Bingbot
User-agent: OAI-SearchBot
User-agent: PerplexityBot
User-agent: Claude-SearchBot
Allow: /

# Agent: allow
User-agent: ChatGPT-User
User-agent: Claude-User
User-agent: Google-Agent
Allow: /

# Training: allow public, block gated
User-agent: GPTBot
User-agent: ClaudeBot
User-agent: Google-Extended
User-agent: CCBot
Allow: /
Disallow: /resources/premium/
Disallow: /client-portal/

# Gives nothing back: block
User-agent: Bytespider
Disallow: /</pre><p></p>
<p>Note that Google-Extended in that list doesn&#8217;t stop Googlebot from fetching the page; it only tells Google not to train Gemini on it. Cloudflare&#8217;s Disallow AI Training setting writes that same line for you, so if you&#8217;re on Cloudflare you can let it manage this block and keep the rest.</p>
<h2>Checks to run this week</h2>
<ul class="mac-checklist">
<li>Open Cloudflare, go to Security, then Settings, and read what the Training control migrated to. It should say Allow or Disallow AI Training. If it says Block, Googlebot is blocked; change it today.</li>
<li>If you want to refuse training, pick Disallow AI Training, not Block, and turn on Bot Preference Sync so the robots.txt lines get written for you.</li>
<li>For Bing, add the NOARCHIVE meta tag on pages you don&#8217;t want used for training, because the robots.txt preference doesn&#8217;t reach Bing until 2027.</li>
<li>Pull a week of origin logs and look for 403 responses to Google&#8217;s published IP ranges. That&#8217;s the only place an edge block shows.</li>
<li>Watch Search Console&#8217;s crawl-stats report for a drop in Googlebot requests since September 15.</li>
<li>Update robots.txt along the lines above, and check the live file, not the one in your theme folder (a stale plugin-generated robots.txt is a classic piece of <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">WordPress technical debt</a>).</li>
<li>Run your key service page through our <a href="https://www.macronimous.com/free-tools/aeo-readiness-checker/">AEO readiness checker</a> to confirm agent bots can actually read what&#8217;s on it once they&#8217;re let in.</li>
</ul>
<p>That&#8217;s about all I know at this point. The rules changed the day before I published this, and Cloudflare has already said AI summaries opt-outs are next, early next year; if you&#8217;ve seen something in your own logs that contradicts any of this, tell me in the comments and I&#8217;ll correct the post with credit. For where this sits in a wider plan, <a href="https://www.macronimous.com/blog/seo-strategy-2026/">our 2026 SEO strategy notes</a> cover the rest of the AI-search picture.</p>
<div class="mac-cta-box">
<h3>Not sure what your Cloudflare settings are doing to Googlebot?</h3>
<p>We audit crawler access, robots.txt, and AI visibility as part of our SEO and AEO retainers, and we&#8217;ll tell you if the fix is a five-minute toggle rather than a project.</p>
<p><a href="https://www.macronimous.com/services/digital-marketing/outsource-seo-services/" class="mac-cta-button">Review my crawler setup</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/should-you-block-ai-bots-website-seo/">Should Your Website Block AI Bots? A Practical 2026 Answer</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/should-you-block-ai-bots-website-seo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress White Screen After a Plugin Update: How to Fix It in 10 Minutes (and When Not To)</title>
		<link>https://www.macronimous.com/blog/wordpress-white-screen-after-plugin-update/</link>
					<comments>https://www.macronimous.com/blog/wordpress-white-screen-after-plugin-update/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Tue, 15 Sep 2026 04:38:13 +0000</pubDate>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<category><![CDATA[WordPress Maintenance]]></category>
		<category><![CDATA[WP Maintenane]]></category>
		<category><![CDATA[WordPress Fixes]]></category>
		<category><![CDATA[WordPress Troubleshooting]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5338</guid>

					<description><![CDATA[<p>A white screen right after a plugin update is almost always that plugin throwing a PHP fatal error. The fix is to deactivate it without going through wp-admin (which you can&#8217;t reach anyway): click the recovery mode link WordPress emailed you, or rename the plugin&#8217;s folder over FTP. The site comes back. Then you decide [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-white-screen-after-plugin-update/">WordPress White Screen After a Plugin Update: How to Fix It in 10 Minutes (and When Not To)</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="mac-direct-answer">
<p><strong>A white screen right after a plugin update</strong> is almost always that plugin throwing a PHP fatal error. The fix is to deactivate it without going through wp-admin (which you can&#8217;t reach anyway): click the recovery mode link <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> emailed you, or rename the plugin&#8217;s folder over FTP. The site comes back. Then you decide what to do about the plugin.</p>
</div>
<p><a href="https://www.macronimous.com/blog/wp-content/uploads/2026/09/WordPress-White-Screen-After-a-Plugin-Update.jpg"><img loading="lazy" decoding="async" width="1734" height="907" class="aligncenter size-full wp-image-5339" src="https://www.macronimous.com/blog/wp-content/uploads/2026/09/WordPress-White-Screen-After-a-Plugin-Update.jpg" alt="WordPress site showing a white screen after a plugin update" /></a>So your site is a blank white page, wp-admin is a blank white page too, and the last thing you did was click Update on a plugin. I&#8217;m going to assume that&#8217;s you, because that&#8217;s who searches for this. You did <em>not</em> break anything permanent, and this is roughly a ten-minute job if you have hosting-panel or FTP access (or can get it from whoever set the site up).</p>
<h2>Make sure it&#8217;s actually the plugin</h2>
<p>It usually is. But &#8220;usually&#8221; has cost people an afternoon, so spend two minutes checking.</p>
<p>Look in the site admin&#8217;s inbox for an email from WordPress with a subject like &#8220;Your Site is Experiencing a Technical Issue.&#8221; WordPress has sent these since version 5.2 (that&#8217;s 2019, so unless your site is a museum piece, you have it). The email names the plugin that failed and includes a special login link. If it names the plugin you just updated, you&#8217;re done diagnosing. Skip to the fix.</p>
<p>No email? Check spam, then check the hosting error log. cPanel calls it &#8220;Errors&#8221;; on Hostinger it&#8217;s under the site&#8217;s PHP settings; on InMotion it&#8217;s a file called <code>error_log</code> sitting in your site root or in <code>wp-admin</code>. You&#8217;re looking for a line that starts with <code>PHP Fatal error</code> and contains a path with <code>/wp-content/plugins/</code> in it. The folder name after that is your culprit.</p>
<p>If the log points at <code>/wp-content/themes/</code> instead, it&#8217;s the theme (same fix, rename the theme folder). If it says &#8220;Allowed memory size exhausted,&#8221; the update tipped a tight PHP memory limit over the edge, and raising it in <code>wp-config.php</code> fixes that. Those are the other two suspects. This post is about the plugin case, which is by far the most common as far as I can tell from the tickets that reach us.</p>
<h2>The fix, three ways, easiest first</h2>
<p>Pick the first one you can do. You don&#8217;t need all three.</p>
<h3>Way 1: the recovery mode link</h3>
<p>This is the one WordPress built for exactly this situation, and almost nobody uses it, which I think is because the email goes to whatever address was typed into Settings, General back when the site was built, which was often the developer&#8217;s address, or a mailbox that was created for the launch and never opened again, so the link sits there unread while the owner is on a forum reading about FTP.</p>
<ul class="mac-checklist">
<li>Find the &#8220;technical issue&#8221; email and click the recovery mode link. It&#8217;s valid for 24 hours by default, so don&#8217;t sit on it.</li>
<li>Log in as normal. You&#8217;ll see a &#8220;Recovery Mode Initialized&#8221; notice, and the broken plugin will be listed as paused.</li>
<li>Go to Plugins, deactivate the paused one, then click &#8220;Exit Recovery Mode&#8221; in the admin bar.</li>
</ul>
<p>The <a href="https://wordpress.org/documentation/article/recovery-mode/" target="_blank" rel="noopener noreferrer">WordPress.org recovery mode page</a> has screenshots if the admin screen looks unfamiliar.</p>
<h3>Way 2: rename the plugin folder</h3>
<p>No email, or the link expired. This is what I&#8217;d do anyway; it takes a minute and it&#8217;s hard to get wrong.</p>
<ul class="mac-checklist">
<li>Open your hosting file manager (cPanel, hPanel, whatever your host calls it) or connect with an FTP client like FileZilla.</li>
<li>Go to <code>wp-content/plugins/</code> and find the folder named after the plugin. It&#8217;s usually obvious: <code>woocommerce</code>, <code>elementor</code>, <code>wordfence</code>.</li>
<li>Rename it. Add <code>-off</code> to the end, so <code>elementor</code> becomes <code>elementor-off</code>. Reload the site.</li>
</ul>
<p>WordPress can&#8217;t find the plugin&#8217;s main file anymore, so it quietly deactivates it and carries on. Your site is back. Once you&#8217;ve sorted the plugin out, rename the folder back and reactivate.</p>
<h3>Way 3: WP-CLI</h3>
<p>Only if you have SSH access and that sentence made sense to you. If it did:</p><pre class="urvanov-syntax-highlighter-plain-tag">cd /path/to/your/wordpress   # the folder that contains wp-config.php
wp plugin list --status=active
wp plugin deactivate the-plugin-slug</pre><p>&nbsp;</p>
<h2>It&#8217;s back. Don&#8217;t walk away yet.</h2>
<p>Clear every cache you have, in this order: the caching plugin (WP Rocket, LiteSpeed Cache, W3 Total Cache), then the hosting cache (most managed hosts have a purge button in the panel), then Cloudflare if the site runs through it. Skip this and you&#8217;ll get a call in an hour from someone still seeing the white page while you&#8217;re looking at a working site. Been there.</p>
<p>Then click three things. The homepage. One inner page that matters, a product page or a services page. The contact form, and actually send it. A site that renders but can&#8217;t take an inquiry is still down as far as your business is concerned.</p>
<p>Now the plugin. You have three options, and I&#8217;d rank them like this:</p>
<ol>
<li>Roll it back to the version that worked. The WP Rollback plugin does this from the Plugins screen for anything on WordPress.org. For a premium plugin, download the previous version from the vendor&#8217;s account area and upload it over FTP.</li>
<li>Leave it deactivated and wait. If the update broke you, it broke a few hundred other people too, and there&#8217;s often a patch within days.</li>
<li>Replace it. If this is the second or third time the same plugin has done this, that&#8217;s your answer.</li>
</ol>
<h2>When you shouldn&#8217;t do this yourself</h2>
<p>This is the part I&#8217;d rather write honestly than write well. Everything above assumes a simple case: one plugin, one update, one error. Some white screens aren&#8217;t simple, and the DIY route makes them worse.</p>
<div class="mac-key-point">
<p>Stop and get a developer in if any of these is true: the error log names a <em>different</em> plugin from the one you updated (that&#8217;s a conflict, and deactivating the wrong one costs you a feature without fixing anything); the site is WooCommerce with orders coming in (a botched rollback can leave orders half-written in the database); there was no backup taken before the update; or the white screen arrived together with a &#8220;this site may be hacked&#8221; warning from Google or your host.</p>
</div>
<p>The last one is a different problem wearing the same symptom. Renaming a plugin folder won&#8217;t help, and poking around in the file system while a site is compromised can destroy the evidence someone needs to clean it properly. That&#8217;s <a href="https://www.macronimous.com/services/cms-development/enterprise-wordpress-website-security-services/">a security cleanup</a>, not a maintenance job, and it&#8217;s the one case where I&#8217;d say close the FTP window and pick up the phone.</p>
<p>On the WooCommerce one, I&#8217;ll admit I&#8217;m more cautious than most Reddit answers. A store that stays white for twenty more minutes while a developer checks the order tables is cheaper than a store that comes back with a broken checkout nobody notices until Monday.</p>
<h2>So it doesn&#8217;t happen again</h2>
<p>Three habits. Nothing clever. Test the update on a staging copy first; most hosts give you one for free now. Take a backup right before you click Update, not the nightly one from twelve hours ago. And update one plugin at a time, so when something breaks you know which one did it. That&#8217;s the whole prevention plan.</p>
<h2>Quick questions</h2>
<div id="ai-wordpress-faq">
<div class="faq-item">
<h3>Will renaming the plugin folder delete my settings?</h3>
<p>No. A plugin&#8217;s settings live in the database. The folder only holds its code. Rename it back, reactivate, and everything is where you left it.</p>
</div>
<div class="faq-item">
<h3>I don&#8217;t have FTP or hosting access. What now?</h3>
<p>Try the recovery mode email first, since that needs no FTP at all. Otherwise, whoever pays the hosting bill can log in to the host&#8217;s panel and either use the file manager or reset the FTP password. The hosting welcome email usually has the panel link.</p>
</div>
<div class="faq-item">
<h3>Can I just delete the plugin folder instead of renaming it?</h3>
<p>You can, and the site will come back the same way. But deleting throws away the code you might want to compare against the old version, and it makes the rollback harder. Renaming is reversible. Deleting isn&#8217;t.</p>
</div>
</div>
<div class="mac-cta-box">
<h3>Site white right now, or tired of holding your breath on every update?</h3>
<p>We fix this same day and set up the staging-first process so it doesn&#8217;t recur. Send us the URL and the name of the plugin you updated.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/cms-development/wordpress-maintenance-services/">Get the site back today</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-white-screen-after-plugin-update/">WordPress White Screen After a Plugin Update: How to Fix It in 10 Minutes (and When Not To)</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/wordpress-white-screen-after-plugin-update/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Testing in Agentic Coding: From Safety Net to Steering Wheel</title>
		<link>https://www.macronimous.com/blog/testing-in-agentic-coding/</link>
					<comments>https://www.macronimous.com/blog/testing-in-agentic-coding/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Thu, 27 Aug 2026 07:33:47 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[AI Web Development]]></category>
		<category><![CDATA[Laravel Development]]></category>
		<category><![CDATA[PHP Development]]></category>
		<category><![CDATA[React development]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[web programming]]></category>
		<category><![CDATA[agentic coding]]></category>
		<category><![CDATA[AI code review]]></category>
		<category><![CDATA[Test-driven development]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[Vibe coding]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5308</guid>

					<description><![CDATA[<p>Testing in agentic coding is no longer a safety net written after the code. Tests are now the specification, the constraint, and the steering wheel for the AI agent itself. Teams that define expected behavior as tests before the agent writes a line ship faster and break less. Teams that let the agent grade its [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/testing-in-agentic-coding/">Testing in Agentic Coding: From Safety Net to Steering Wheel</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/08/Testing-in-Agentic-Coding-scaled.jpg"><img loading="lazy" decoding="async" width="2560" height="1352" src="https://www.macronimous.com/blog/wp-content/uploads/2026/08/Testing-in-Agentic-Coding-scaled.jpg" alt="Testing in Agentic Coding: " class="aligncenter size-full wp-image-5310" /></a>
<div class="mac-direct-answer">
<p><strong>Testing in <a href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">agentic coding</a></strong> is no longer a safety net written after the code. Tests are now the specification, the constraint, and the steering wheel for the <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">AI</a> agent itself. Teams that define expected behavior as tests before the agent writes a line ship faster and break less. Teams that let the agent grade its own homework ship confident-looking bugs.</p>
</div>
<p>We have been shipping a lot of apps with agents lately: Claude Code on some projects, our developers on Kilocode and Codex on others. The developers adapted fast. The question that kept bothering me was our test engineers, and whether twenty years of testing discipline just became obsolete overnight. This post is my answer: it did not. It became more important, and it moved to a different place in the process. Along the way I will also answer the question that follows immediately after: if agents write the code, who reviews it, and does agent-reviewing-agent even make sense?</p>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#safety-net-steering-wheel">The safety net became the steering wheel</a></li>
<li><a href="#agent-without-tests">What happens when an agent codes without tests</a></li>
<li><a href="#test-driven-prompting">Test-driven prompting: write the contract, not the code</a></li>
<li><a href="#regression-first">Fix bugs backwards: regression test first, patch second</a></li>
<li><a href="#agent-code-review">Can an agent review agent code?</a></li>
<li><a href="#team-lead-changes">What changes for the team lead</a></li>
<li><a href="#testers-promoted">Your testers just got promoted</a></li>
</ul>
</div>
<h2 id="safety-net-steering-wheel">The safety net became the steering wheel</h2>
<p>For twenty years, tests sat downstream of code. You wrote the feature, then you wrote tests to prove it worked, and everyone quietly accepted that half the time the tests came late or not at all. Tests were insurance. Skipping them was a debt, not a disaster.</p>
<p>Agentic coding flipped the direction of that arrow. When Claude Code, Cursor, or any autonomous agent writes and modifies code in a loop, the test suite is the only channel through which reality reaches the agent. The agent cannot see your product spec. It cannot sit in your standup. What it can do, thousands of times an hour, is run a test and read the result. That makes tests the primary steering mechanism, not the final checkpoint. Anthropic&#8217;s own <a href="https://code.claude.com/docs/en/best-practices" target="_blank" rel="noopener noreferrer">Claude Code best practices</a> put test-driven workflows at the center for exactly this reason: a red-to-green cycle gives the agent unambiguous feedback it can iterate against without a human in the loop.</p>
<p>Here is the position I will defend: in an agentic team, writing tests after the code is not a smaller version of testing. It is theater.</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-5307" src="https://www.macronimous.com/blog/wp-content/uploads/2026/08/testing-agentic-coding-vs-traditional-development.jpg" alt="Diagram comparing testing as a safety net in traditional development versus testing as driver and constraint in agentic vibe coding" width="1600" height="893"></p>
<h2 id="agent-without-tests">What happens when an agent codes without tests</h2>
<p>The failure modes are predictable, and if you manage a web team using AI agents, you have already seen at least one of them.</p>
<ul>
<li><strong>Hallucinated correctness.</strong> The agent reports &#8220;done, all working&#8221; for code it never executed against a real assertion. It is not lying. It has no ground truth to check against, so its confidence is a language pattern, not a verdict.</li>
<li><strong>Fix one, break three.</strong> Ask an agent to patch a Laravel endpoint and it will happily rewrite a shared helper, silently changing behavior in two other controllers. Without a regression suite, nobody notices until a client does.</li>
<li><strong>Spec drift.</strong> Over a long session, the agent&#8217;s idea of the feature slowly diverges from yours. Each individual change looks reasonable. The sum is a checkout flow that does something you never asked for.</li>
</ul>
<p>There is a fourth failure mode that surprises people: unlike a human developer, an agent almost never says no. A senior developer will push back on a bad requirement. An agent builds whatever you describe, including the unwise version, and builds it convincingly. Tests are the mechanism that replaces the pushback you lost.</p>
<h2 id="test-driven-prompting">Test-driven prompting: write the contract, not the code</h2>
<p>The practical shift for developers is that you stop typing assertions by hand and start defining behavior up front, in plain language or minimal test skeletons, then instruct the agent to write code that makes those tests pass. Kent Beck, who literally wrote the book on TDD, calls this discipline the difference between vibe coding and <a href="https://newsletter.kentbeck.com/p/augmented-coding-beyond-the-vibes" target="_blank" rel="noopener noreferrer">augmented coding</a>: in one you only care that the behavior looks right, in the other you own the tests and the design while the agent does the typing.</p>
<p>The working sequence looks like this:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">1. "Write failing tests for the coupon validation endpoint.
   Expected: 422 on expired codes, 200 with discount object on valid codes,
   409 when the coupon is already redeemed. No implementation yet."
2. "Run the tests. Confirm they all fail."
3. Commit the failing tests.
4. "Now implement until every test passes.
   Do not modify the tests."</pre><p>&nbsp;</p>
<p>Step 3 is not optional. Agents will sometimes edit a test to make it pass instead of fixing the implementation. Committing the failing tests first means any tampering shows up in the diff, and you can revert it in seconds. That one git commit is doing the supervision work a human reviewer used to do line by line.</p>
<p>For web teams, the same pattern applies at every layer: Pest or PHPUnit for the API, Vitest for React components, and <a href="https://playwright.dev/docs/intro" target="_blank" rel="noopener noreferrer">Playwright</a> for the end-to-end flows that actually pay the bills, like login, cart, and checkout. The agent can write and run all of them. Your job is deciding what they must assert.</p>
<h2 id="regression-first">Fix bugs backwards: regression test first, patch second</h2>
<p>When a bug surfaces, the old reflex is to dive into the implementation. The agentic reflex should be different: prompt the agent to write a test that reproduces the bug, watch it fail, and only then allow the agent to generate the fix.</p>
<p>This ordering matters for two reasons. First, a reproducing test proves the agent understood the bug; a patch without one proves nothing. Second, that test stays in the suite forever, so the same agent (or the next one, or the junior developer six months from now) cannot reintroduce the bug without the pipeline going red. Every incident becomes a permanent constraint. Over a year, this is how a codebase touched by agents gets more stable instead of less. This is the same discipline we described in <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a>, applied to defects instead of features.</p>
<div class="mac-key-point">
<p>In agentic development, the test suite is the only part of the codebase a human must fully own. Everything else is negotiable.</p>
</div>
<h2 id="agent-code-review">Can an agent review agent code?</h2>
<p>This is the question that sounds like a trap: if the agent wrote the code, having an agent review it feels like a developer testing his own work. Sometimes it is exactly that, and sometimes it is not. The difference is context, and it is worth being precise about.</p>
<p>An agent reviewing code inside the same session that wrote it is worthless. It carries the full memory of its own reasoning, so it re-approves its own assumptions, the same way you cannot proofread your own email five seconds after sending it. But a <strong>fresh agent session with no memory of the implementation</strong> is a genuinely different reviewer. It reads the diff cold, with no attachment to the decisions behind it, which is closer to a new team member reviewing a PR than to a developer grading himself. Anthropic recommends this writer/reviewer split as a standard pattern for the same reason.</p>
<p>So agent review is real, but it is narrow. A fresh agent catches logic errors, missed edge cases, and deviations from the test contract. What it cannot do is be accountable. Both agents still share the same training, the same blind spots, and the same inability to know your client, your compliance obligations, or your business. That is where the human stays, and not as a formality:</p>
<ul>
<li><strong>Owning the behavior contract.</strong> A human decides what the tests must assert. Agents can propose edge cases; a person approves them.</li>
<li><strong>Reviewing the tests, not the volume.</strong> Nobody honestly reads 800 generated lines. A human reads the 60 lines of tests that govern them, plus a spot-check of anything touching money, auth, or data deletion.</li>
<li><strong>Architectural and product judgment.</strong> Whether this feature should exist, whether this dependency is acceptable, whether this shortcut will hurt in a year. No test suite encodes that.</li>
<li><strong>The merge.</strong> A human presses the button, and that name in the git history means something a bot&#8217;s cannot.</li>
</ul>
<p>Put plainly: shipping unreviewed code was never acceptable, and agents do not change that. They change what review looks like. Test contract reviewed by a human, implementation reviewed by a fresh agent, judgment calls and the merge held by a human. That is not weaker than traditional code review. Done honestly, it is stricter, because the contract is explicit instead of living in a reviewer&#8217;s head.</p>
<h2 id="team-lead-changes">What changes for the team lead</h2>
<p>If you run a web team, the shift is bigger for you than for your developers. The summary:</p>
<table class="styled-table">
<thead>
<tr>
<th>Dimension</th>
<th>Traditional workflow</th>
<th>Agentic workflow</th>
</tr>
</thead>
<tbody>
<tr>
<td>Role of tests</td>
<td>Validate finished code</td>
<td>Specify and constrain the agent</td>
</tr>
<tr>
<td>When tests are written</td>
<td>After implementation, if time allows</td>
<td>Before the agent writes code</td>
</tr>
<tr>
<td>Debugging</td>
<td>Manual analysis, then refactor</td>
<td>Regression test first, then prompted fix</td>
</tr>
<tr>
<td>What reviewers read</td>
<td>The implementation diff</td>
<td>The tests, then the diff</td>
</tr>
<tr>
<td>QA position</td>
<td>End of the cycle</td>
<td>Start of the cycle, encoded as tests</td>
</tr>
</tbody>
</table>
<p>The review change deserves emphasis. An agent can generate 800 lines of plausible React in a minute; no human reviews that honestly. What a human can review is the 60 lines of tests that define what those 800 lines must do. Review the contract, spot-check the implementation. Where the <a href="https://www.macronimous.com/blog/web-development-life-cycle-process-flow-diagram/">web development life cycle</a> used to place QA as a phase, agentic teams place it as a gate: CI runs the full suite on every agent-generated commit, and the agent is simply not allowed to ship red. Budget-wise, the hours you used to spend on post-build QA move upstream into behavior definition. The total testing effort does not shrink. It moves to the front, where it is cheap.</p>
<p>A policy checklist worth adopting before your team&#8217;s next sprint:</p>
<ul class="mac-checklist">
<li>Every agent-built feature starts from tests the agent was given or asked to generate first, committed while still failing</li>
<li>Agents are instructed never to modify existing tests; any test change requires a human commit</li>
<li>No agent-generated PR merges with a red pipeline, no exceptions for &#8220;it works locally&#8221;</li>
<li>Every production bug gets a reproducing regression test before its fix is accepted</li>
<li>Critical user flows (auth, cart, checkout, payments) have end-to-end coverage the agent must keep green</li>
<li>Reviewers read tests first and sign off on behavior, not line-by-line implementation</li>
</ul>
<h2 id="testers-promoted">Your testers just got promoted</h2>
<p>The uncomfortable conclusion for managers: the people on your team who are good at defining expected behavior, edge cases, and failure conditions just became your highest-leverage engineers. That skill used to be undervalued as &#8220;QA work.&#8221; It is now the interface through which your team controls its fastest developer, the agent. The developers who thrive in this model are the ones who can hold the whole behavior of a feature in their head and write it down precisely, which is also the skill we argued matters most in <a href="https://www.macronimous.com/blog/vibe-coding-for-web-developers-amplify-your-flow-state-with-ai/">vibe coding for web developers</a> and in <a href="https://www.macronimous.com/blog/writing-clean-code-with-ai/">writing clean code with AI</a>.</p>
<p>So no, test cases did not die in the age of agentic coding. They stopped being paperwork and became the steering wheel. The teams that treat them that way will let agents run fast safely. The teams that skip them will discover that an agent with no constraints is just a very fast way to write next quarter&#8217;s incident report.</p>
<div class="mac-cta-box">
<h3>Putting agents to work on a real web project?</h3>
<p>We build and manage <a href="https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/">web development</a> sprints where AI agents do the heavy lifting inside test-gated pipelines, so speed never comes at the cost of stability.</p>
<p><a href="https://www.macronimous.com/agile-web-development/" class="mac-cta-button">Plan a test-gated sprint with us</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/testing-in-agentic-coding/">Testing in Agentic Coding: From Safety Net to Steering Wheel</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/testing-in-agentic-coding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress Plugin Security Starts as a Visibility Problem</title>
		<link>https://www.macronimous.com/blog/wordpress-plugin-security-visibility/</link>
					<comments>https://www.macronimous.com/blog/wordpress-plugin-security-visibility/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Tue, 25 Aug 2026 12:20:13 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Macronimous]]></category>
		<category><![CDATA[MCP]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[WordPress Maintenance]]></category>
		<category><![CDATA[MCP Server]]></category>
		<category><![CDATA[technical debt]]></category>
		<category><![CDATA[Wordpress plugins]]></category>
		<category><![CDATA[Wordpress security]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5312</guid>

					<description><![CDATA[<p>WordPress plugin security is usually discussed as a code quality problem, but for most live sites it starts as a visibility problem. The information you would need to judge an estate exists in the database, in cron, and in public advisory feeds. It is just scattered across places no admin screen shows you. The argument [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-plugin-security-visibility/">WordPress Plugin Security Starts as a Visibility Problem</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/08/You-cant-secure-what-you-cant-see.jpg"><img loading="lazy" decoding="async" width="2240" height="1260" class="aligncenter size-full wp-image-5316" src="https://www.macronimous.com/blog/wp-content/uploads/2026/08/You-cant-secure-what-you-cant-see.jpg" alt="WordPress Security" /></a>
<div class="mac-direct-answer">
<p><strong><a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> plugin security</strong> is usually discussed as a code quality problem, but for most live sites it starts as a visibility problem. The information you would need to judge an estate exists in the database, in cron, and in public advisory feeds. It is just scattered across places no admin screen shows you.</p>
</div>
<h2>The argument nobody wins</h2>
<p>The criticism is familiar. WordPress is insecure because anyone can bolt forty plugins from forty strangers onto a site and never look at them again. Say it in any developer forum and you will get agreement within a minute.</p>
<p>The defense is equally familiar. That is not WordPress&#8217;s fault. It is the site owner&#8217;s fault for installing carelessly, or the host&#8217;s fault, or the plugin author&#8217;s fault. I am on the defending side more often than not, and I have made the case that <a href="https://www.macronimous.com/blog/ai-website-builder-vs-wordpress-for-seo/">WordPress still beats the AI site builders</a> where it counts.</p>
<p>Here is my problem with that exchange, as someone who has run a <a href="https://www.macronimous.com/blog/is-headless-wordpress-worth-it/">WordPress development</a> business for years: the defense is technically correct and practically useless. &#8220;Be more careful&#8221; is not a mechanism. It gives nobody a way to act differently on Monday morning.</p>
<p>Both sides are arguing about blame. Almost nobody is arguing about what a site owner can actually see.</p>
<h2>Your plugins screen shows three fields</h2>
<p>Open any WordPress admin. The plugins screen gives you a name, a version number, and a description the author wrote about themselves.</p>
<p>That is the entire interface most people have for reasoning about risk. It does not tell you which of those plugins has a published vulnerability at the version you actually have installed, as opposed to merely appearing somewhere in an advisory database. It does not tell you which one loads 19 KB of autoloaded options into every single page request. It does not tell you that the plugin you deleted in 2019 left a scheduled job still firing every hour.</p>
<p>None of that is hidden. It sits in <code>wp_options</code>, in the cron array, in <code>SHOW TABLE STATUS</code>, and in public feeds like <a href="https://www.wpvulnerability.net/" target="_blank" rel="noopener noreferrer">the WPVulnerability database</a>. Getting to it means being a developer with a database client and a free afternoon.</p>
<p>So we have an ecosystem whose central criticism is a security one, and the people responsible for those sites cannot answer basic questions about them without writing SQL. That gap is where I think the useful work is. It is the same shape as the <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">technical debt that accumulates quietly in WordPress</a>: not dramatic, not visible, and expensive later.</p>
<h2>What a ten-year-old site leaves behind</h2>
<p>We pointed a read-only audit at one of our own sites. It has been alive since 2015 and it is not unusual in any way.</p>
<p>The orphaned tables told a story. A contact form plugin, long since removed, had left three tables holding 71 form submissions and 642 field-value rows. Names, email addresses, and messages from people who contacted the business years ago, sitting in tables that no privacy tool on that site can see, because no installed plugin claims them.</p>
<p>An invoicing plugin had left eight tables, all empty. Installed, never really used, deleted, and never cleaned up. A background job queue had 35 queued actions and 93 log rows belonging to a plugin nobody could name.</p>
<p>Nothing there is a vulnerability in the CVE sense. No scanner would flag it. But if you had asked me before that audit whether we held a decade of stranded contact form submissions on that site, I would have said no, confidently, and I would have been wrong.</p>
<div class="mac-key-point">
<p>You cannot make a judgment about an estate you cannot see. Most <a href="https://www.macronimous.com/blog/wordpress-to-headless/">WordPress security</a> advice assumes visibility that nobody actually has.</p>
</div>
<h2>So I built something that can only look</h2>
<p>The result is <a href="https://wordpress.org/plugins/auditra/" target="_blank" rel="noopener noreferrer">Auditra</a>, a free plugin that turns the site it runs on into a read-only <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer">MCP</a> server. You enable an endpoint, generate a token, paste the connection URL into an <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> client, and then ask questions in plain language. Which plugins are vulnerable at the versions installed. What is bloating the options table. What did deleted plugins leave behind.</p>
<p>The interesting part is not the tool list. It is the three things it refuses to do, each of which cost me something.</p>
<p>It cannot change anything. No activating, no deactivating, no updating, no writing to the database at all. That is structural rather than a promise: there is no write call anywhere in the codebase, and the build fails if one is ever added. A tool that can only look has nothing to sell you afterward.</p>
<p>It does not score anything. No grades, no risk numbers, no &#8220;this plugin is dangerous.&#8221; Scores sell, which is precisely why so much security tooling has them and precisely why I do not trust them. The plugin returns facts with documented thresholds and leaves the judgment to the model reading them, which also means the analysis improves as models improve without me shipping anything. That is the same principle behind <a href="https://www.macronimous.com/blog/controlled-ai-coding/">keeping AI on a short leash while coding</a>: give it accurate context, not conclusions.</p>
<h2>Saying &#8220;I don&#8217;t know&#8221; is the whole design</h2>
<p>The decision I spent longest on sounds trivial. If the plugin could not check anything at all, what should it return?</p>
<p>The obvious answer is an empty list of findings. That is what most tools do, and it is wrong, because an empty list reads as <em>clean</em>. A security tool that implies &#8220;clean&#8221; when it checked nothing is worse than no tool, because it manufactures confidence that was never earned.</p>
<p>So it returns no findings list at all in that case. Not an empty one. A response saying what could not be checked, why, when the data was last fetched, and when it will retry. When only some plugins could be checked, the ones that were skipped are named individually. Every data source reports its own coverage.</p>
<p>That is a small technical decision, but it is the entire argument in miniature. The reason WordPress security discourse is so poor is that too much of the tooling is confidently wrong. Scanners emit scores. Plugins claim protection. Almost nothing ever says &#8220;I could not check that.&#8221; I would rather ship something that admits ignorance loudly than something that looks authoritative and is occasionally lying to you.</p>
<h2>What this does not fix</h2>
<p>Plenty. Seeing that a plugin has been unmaintained for three years does not patch it. Knowing an orphaned table holds old contact submissions does not delete it, and deliberately so, because a read-only tool cannot make that mistake on your behalf.</p>
<p>It also needs an AI client on the other end, which today means a narrower audience than the WordPress user base. I would rather say that plainly than pretend otherwise.</p>
<p>And there is a fair objection I should answer rather than dodge: this adds an HTTP endpoint that returns information about your site, which is a new surface. True. It ships disabled and answers 404 until an administrator turns it on, it requires a token compared in constant time, it is rate-limited, it is revocable in one click, and it exposes no post content, no user accounts, no credentials, and no option values. Only names and sizes. That is a considered trade, not an accident, and the readme spells out exactly what an enabled endpoint exposes so nobody has to take my word for it.</p>
<p>The plugin ecosystem is the best thing about WordPress. It is also the thing that makes a ten-year-old site an archaeological dig. I do not think that tension is going away, and I do not think another scanner with a letter grade helps. Better visibility might, a little. That is the whole intention, and it is a small one. The rest of the argument belongs to the ecosystem, which has been having it for fifteen years and will probably still be having it in another fifteen.</p>
<div class="mac-cta-box">
<h3>Not sure what your plugin estate is actually doing?</h3>
<p>We maintain WordPress sites that have been running for a decade or more, and we start by finding out what is really on them.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/cms-development/wordpress-maintenance-services/">See our WordPress maintenance service</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-plugin-security-visibility/">WordPress Plugin Security Starts as a Visibility Problem</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/wordpress-plugin-security-visibility/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Agentic Coding Mistakes: Lessons From a Real App build</title>
		<link>https://www.macronimous.com/blog/agentic-coding-domain-knowledge/</link>
					<comments>https://www.macronimous.com/blog/agentic-coding-domain-knowledge/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Thu, 20 Aug 2026 06:57:27 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[MCP Development]]></category>
		<category><![CDATA[Vibe Coding]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[agentic coding]]></category>
		<category><![CDATA[AI code review]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[invoice software]]></category>
		<category><![CDATA[MCP]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5297</guid>

					<description><![CDATA[<p>Agentic coding tools build the most conventional version of whatever you ask for. We asked for an invoicing system and got textbook accounting software, including a void feature that would have carried a GST liability on unpaid export invoices. Two diff-backed incidents from that build show why the fix is domain knowledge in the spec, [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">Agentic Coding Mistakes: Lessons From a Real App build</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/08/Agentic-Coding-Mistakes.jpg"><img loading="lazy" decoding="async" width="1729" height="910" src="https://www.macronimous.com/blog/wp-content/uploads/2026/08/Agentic-Coding-Mistakes.jpg" alt="Agentic Coding Mistakes" class="aligncenter size-full wp-image-5303" /></a>
<div class="mac-direct-answer">
<p><strong><a href="https://www.macronimous.com/blog/testing-in-agentic-coding/">Agentic coding</a> tools</strong> build the most conventional version of whatever you ask for. We asked for an invoicing system and got textbook accounting software, including a void feature that would have carried a GST liability on unpaid export invoices. Two diff-backed incidents from that build show why the fix is domain knowledge in the spec, not more hours spent reviewing generated code.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#training-data-app">The app an AI builds is the app in its training data</a></li>
<li><a href="#void-incident">Incident one: the agent built a void feature and made it law</a></li>
<li><a href="#numbering-incident">Incident two: numbering that was right everywhere except India</a></li>
<li><a href="#mistake-that-never-happened">The mistake that never happened</a></li>
<li><a href="#mcp-connector">The MCP connector: agentic defaults trust too much</a></li>
<li><a href="#specs-beat-review">Review catches what you know. Specs prevent what you don&#8217;t.</a></li>
<li><a href="#open-source">The bottom line, and the code</a></li>
</ul>
</div>
<h2 id="training-data-app">The app an AI builds is the app in its training data</h2>
<p>The standard advice for AI-assisted development is simple: let the agent write the code, then review it. We follow a stricter version of that ourselves and wrote it up as <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a>. But this build taught us where that advice runs out. Review catches the mistakes you already know to look for. It does nothing about the mistakes that look like correct code.</p>
<p>Some context, kept short. We run a software-export business in India, and export invoicing follows rules that generic tools don&#8217;t model. Exports of services are <a href="https://taxinformation.cbic.gov.in/content/html/tax_repository/gst/acts/2017_IGST_Act/active/chaptervii/section16_v1.00.html" target="_blank" rel="noopener noreferrer">zero-rated under Section 16 of the IGST Act</a>, payment must arrive in convertible foreign exchange, the bank issues a FIRC (Foreign Inward Remittance Certificate) as proof, each remittance carries an RBI purpose code, and invoice numbers run gapless per financial year, which in India starts on April 1. This is our working understanding, not tax advice; confirm specifics with your chartered accountant.</p>
<p>Because no off-the-shelf tool ties a FIRC to the invoice it settles, we built our own system, with Claude Code doing most of the typing. The agent was fast, confident, and produced clean PHP. It also produced two failures that never threw an error, never failed a test we had at the time, and would have passed any code review that judged the code as code.</p>
<h2 id="void-incident">Incident one: the agent built a void feature and made it law</h2>
<p>Every accounting tutorial, every open-source invoice app, every SaaS product the model has ever seen handles a wrong invoice the same way: void it, keep the number, never delete. So the agent built exactly that. Not a suggestion, a complete feature: a Void button, a confirm dialog, a reinstate action, and a comment declaring the behavior as rule &#8220;&sect;1.10&#8221; of its own spec. From <code>invoice-view.php</code> at commit <code>99d6730</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Tax-invoice document (&sect;2). Born paid — status is `issued` or `void`,
 * voided, never deleted (&sect;1.10). */
if ($action === 'void' &amp;&amp; $inv['status'] === 'issued') {
    $DB-&gt;prepare("UPDATE invoices SET status='void' WHERE id=?")-&gt;execute([$id]);
    flash('Invoice voided. The number is retained (never reused).');
}</pre><p>&nbsp;</p>
<p>As generic accounting software, this is correct. As software for an Indian exporter, it is a liability generator. In our reading of GST, an issued tax invoice creates a tax liability whether or not you later mark it void. A wrongly issued invoice has to be removed and its proforma reverted, so the liability never exists on paper. We caught it in the functional-gap review, and the fix went in as commit <code>c9f4107</code>, whose message states the reasoning better than any paragraph I could write here:</p>
<blockquote>
<p>&#8220;invoice-view: replace Void with &#8216;Undo conversion&#8217; — deletes a wrongly converted invoice, reverts its proforma to Paid, frees the FY number (an invoice that exists is a GST liability, so wrong ones are removed).&#8221;</p>
</blockquote>
<div class="mac-key-point">
<p>This was a jurisdiction error made confidently. The agent generated the correct feature for the wrong country, then wrote it into its own spec as a rule.</p>
</div>
<p>That last part deserves a beat. The agent didn&#8217;t hedge. It codified the pattern as canonical, complete with a section number, and every later piece of generated code would have treated void as settled behavior. A wrong assumption an agent writes into its own spec compounds with every subsequent prompt.</p>
<h2 id="numbering-incident">Incident two: numbering that was right everywhere except India</h2>
<p>The first version of <code>next_invoice_number()</code>, from the initial commit <code>f6b7863</code>, made two default choices in one function:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">$st = $db-&gt;prepare('SELECT prefix, next_seq FROM companies WHERE id = ? FOR UPDATE');
// ...
return sprintf('%s-%s-%03d', $row['prefix'], date('Y', strtotime($issueDate)), $seq);</pre><p>&nbsp;</p>
<p>One global counter per company, and <code>date('Y')</code>: the calendar year. Reasonable everywhere the model&#8217;s training data comes from. Wrong in India, where invoice sequences reset on April 1 for the new financial year, and where our proforma and its resulting tax invoice must share a sequence number so the books line up. The catch, again, came from a review question, not from the code: &#8220;every business year, April 1st, we change the invoice numbers.&#8221; That single sentence forced a rebuild into per-company, per-document-type, per-FY gapless sequences.</p>
<p>Then the second layer, and this one is the more interesting failure. The rebuilt version used <code>INSERT IGNORE</code> plus <code>SELECT ... FOR UPDATE</code> plus <code>UPDATE</code>, a sequence that <a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks.html" target="_blank" rel="noopener noreferrer">deadlocks under InnoDB</a> when two transactions create invoices at the same moment. No human review caught this. It was caught by a concurrency test that existed for one reason: someone asked &#8220;what if a team member creates an invoice on their machine at the same time?&#8221; The question generated the test; the test caught what eyes could not. The fix was a single atomic statement.</p>
<p>That is the arc worth remembering: naive, then domain-corrected, then concurrency-corrected, each step with a diff. And the mechanism differed. The FY miss was caught by knowledge. The deadlock was caught by a test that a question produced. If your process only has &#8220;review the diff,&#8221; you get the first catch and miss the second.</p>
<h2 id="mistake-that-never-happened">The mistake that never happened</h2>
<p>I expected a third incident. Agentic tools routinely model payment as a boolean, an <code>is_paid</code> flag on the invoice row, because that&#8217;s what tutorial invoice apps do. It never happened here. The very first schema, commit <code>f39c0b2</code>, already had a <code>payments</code> table with amount, currency, and FIRC linkage as a first-class record.</p>
<p>Why? Because the FIRC requirement was in the spec before the agent generated a line of code. When the spec says every foreign payment must carry its remittance certificate and purpose code, a boolean cannot satisfy it, so the wrong shape never gets generated. Nobody had to catch this mistake, because the constraint made it impossible to make.</p>
<table class="styled-table">
<thead>
<tr>
<th>What the agent defaults to</th>
<th>What an Indian exporter needs</th>
<th>How it was handled</th>
</tr>
</thead>
<tbody>
<tr>
<td>Void a wrong invoice, keep the number</td>
<td>Remove the invoice, revert the proforma, free the FY number</td>
<td>Built wrong, caught in review</td>
</tr>
<tr>
<td>One counter, calendar year</td>
<td>Gapless per-FY sequences resetting April 1, proforma and invoice matched</td>
<td>Built wrong, caught in review, then again by a test</td>
</tr>
<tr>
<td>An <code>is_paid</code> boolean</td>
<td>Payments as first-class records with FIRC and purpose code</td>
<td>Never built wrong: the constraint was in the spec</td>
</tr>
</tbody>
</table>
<p>Three rows, one pattern. The only mistake that cost nothing was the one the spec prevented.</p>
<h2 id="mcp-connector">The MCP connector: agentic defaults trust too much</h2>
<p>The system has an <a href="https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/">MCP</a> connector, so an accountant can open Claude Desktop and type &#8220;create and send an invoice to Acme Ltd for the March retainer&#8221; without ever logging into the app. The agentic default for this kind of connector is full write access, because that&#8217;s the frictionless demo. We went the other way. The connector is a thin layer over the app&#8217;s own token-authenticated API: writes are draft-first and require confirmation, every AI-initiated change is audit-logged, and the token is role-scoped so the <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> cannot see or touch more than that accountant could. It&#8217;s stateless, single-request JSON, which lines up with where the <a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" target="_blank" rel="noopener noreferrer">2026-07-28 MCP specification</a> took the protocol. The AI holds no business logic. The invoice system stays the single source of truth, which matters for the same reason everything above matters: an agent that improvises accounting logic is an agent that improvises liabilities.</p>
<h2 id="specs-beat-review">Review catches what you know. Specs prevent what you don&#8217;t.</h2>
<p>Agentic tools never say &#8220;that&#8217;s unwise.&#8221; A human developer with export clients would have asked about the financial year before writing a numbering function. The agent shipped <code>date('Y')</code> without a flicker of doubt, and it would ship the void feature again tomorrow. We&#8217;ve written before about <a href="https://www.macronimous.com/blog/writing-clean-code-with-ai/">keeping AI-generated code clean</a> and about why <a href="https://www.macronimous.com/blog/vibe-coding-for-web-developers-amplify-your-flow-state-with-ai/">vibe coding needs guardrails before production</a>, but this build sharpened the point: cleanliness was never the problem. Every one of these mistakes was clean.</p>
<p>So put the domain in writing before the agent starts. For anything with statutory weight, our pre-build spec now answers:</p>
<ul class="mac-checklist">
<li>Which jurisdiction&#8217;s rules govern this system, and which documents are statutory records?</li>
<li>What is the full lifecycle of each document, including what may be voided, deleted, reverted, or never touched?</li>
<li>What are the numbering rules: sequence scope, gaplessness, and which fiscal calendar they follow?</li>
<li>What proof documents (FIRC, purpose codes, certificates) must exist as first-class records, not fields?</li>
<li>What happens when two people do the same thing at the same time, and which test proves it?</li>
<li>If an AI connector exists, what is its write model: scope, confirmation, and audit trail?</li>
</ul>
<p>Every &#8220;what if&#8221; question in that list is a test waiting to be written. Ask it early and the agent generates the right shape from the first commit, the way the payments table proved.</p>
<h2 id="open-source">The bottom line, and the code</h2>
<p>The agent was worth it. It typed the app in a fraction of the time it would have taken us by hand, and both incidents were cheaper to fix than a single GST notice would have been to receive. But the value of the build lived in the two moments a human said &#8220;not in India&#8221; and the one moment a spec made the question unnecessary. Speed came from the agent. Correctness came from the domain.</p>
<p>We&#8217;re releasing the system under MIT for other Indian software exporters and freelancers with the same compliance shape: a single-business kit that self-installs on ordinary PHP and MySQL shared hosting, with your data in your own database. It is deliberately narrow. Built around Indian GST and export-of-services rules, it is not a fit for domestic billing at other tax rates or for non-Indian firms without changes. <!-- INSERT PUBLIC REPO URL HERE, or remove this sentence if the repo isn't live at publish time: --> The repository is on GitHub; if you find a rule we got wrong, an issue telling us so is the most useful contribution you can make.</p>
<div class="mac-cta-box">
<h3>Shipping AI-built code to production?</h3>
<p>We pair agentic speed with 24 years of review discipline, on React, PHP, and <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> builds where a wrong default has real-world costs.</p>
<p><a href="https://www.macronimous.com/services/custom-web-development/" class="mac-cta-button">Get your AI-built app reviewed</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">Agentic Coding Mistakes: Lessons From a Real App build</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/agentic-coding-domain-knowledge/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>GitHub for FTP Users in the age of Agentic coding:</title>
		<link>https://www.macronimous.com/blog/github-for-ftp-users-in-the-age-of-agentic-coding/</link>
					<comments>https://www.macronimous.com/blog/github-for-ftp-users-in-the-age-of-agentic-coding/#respond</comments>
		
		<dc:creator><![CDATA[Benny]]></dc:creator>
		<pubDate>Wed, 05 Aug 2026 12:23:18 +0000</pubDate>
				<category><![CDATA[Welcome]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5273</guid>

					<description><![CDATA[<p>Version control is a system that records every change to your project so you can see what changed, when, and by whom, and roll back to any earlier state. If you have spent years dragging files into FileZilla, you already understand most of it: a repository is your project folder, a commit is a save [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/github-for-ftp-users-in-the-age-of-agentic-coding/">GitHub for FTP Users in the age of Agentic coding:</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/07/GitHubVsFTPin2026.jpg"><img loading="lazy" decoding="async" class="aligncenter wp-image-5276 size-large" src="https://www.macronimous.com/blog/wp-content/uploads/2026/07/GitHubVsFTPin2026-1024x585.jpg" alt="GitHub vs FTP" width="1024" height="585" /></a>
<div class="mac-direct-answer">
<p><strong>Version control</strong> is a system that records every change to your project so you can see what changed, when, and by whom, and roll back to any earlier state. If you have spent years dragging files into FileZilla, you already understand most of it: a repository is your project folder, a commit is a save with a note attached, and a push is an upload that never destroys the older version.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">On this page</p>
<ul>
<li><a href="#already-move-files">You already move files. You just never kept the receipts.</a></li>
<li><a href="#git-vs-github">Git and GitHub are not the same thing</a></li>
<li><a href="#rosetta-stone">The FTP-to-Git Rosetta Stone</a></li>
<li><a href="#three-things">The three things FTP simply cannot do</a></li>
<li><a href="#where-it-breaks">Where the FTP comparison breaks</a></li>
<li><a href="#why-wordpress">Why WordPress developers got away without it</a></li>
<li><a href="#getting-started">Starting without the terminal fear</a></li>
</ul>
</div>
<h2 id="already-move-files">You already move files. You just never kept the receipts.</h2>
<p>I have been uploading files to servers since SmartFTP and WS_FTP were the normal way to do it. FileZilla and Cyberduck are still open on my Mac most weeks. There is a specific muscle memory to FTP: edit the file, drag it into the client, watch the progress bar fill, done. It works. It has worked for over twenty years.</p>
<p>Here is the catch. FTP has exactly one job: take the file on your machine and put it on the server, on top of whatever was there before. The server holds one version of the truth, the current one. The moment you upload a broken file over a working one, the working one is gone. No note about what you changed. No way back. If a colleague edited the same file an hour ago, your upload quietly erases their work.</p>
<p>FTP has no memory, and it was never trying to have one. Version control is the memory FTP never had.</p>
<h2 id="git-vs-github">Git and GitHub are not the same thing</h2>
<p>Before the vocabulary, clear up the one confusion that trips up everyone who picked this up by osmosis. Git and GitHub are two different things.</p>
<p>Git is the version control system. It runs on your own machine and keeps the full history of your project in a hidden folder. It does not need the internet to work. <a href="https://git-scm.com/" target="_blank" rel="noopener noreferrer">Git itself</a> is free, open source, and describes itself as a distributed version control system, which is a precise way of saying every copy carries the entire history.</p>
<p>GitHub is a website that hosts Git repositories and adds a layer of collaboration on top: a place to keep the shared copy, review changes, and discuss them. GitLab and Bitbucket do the same job. Almost every term below is a Git concept. Only one of them, the pull request, actually belongs to GitHub. People say &#8220;GitHub&#8221; the way they say &#8220;Google it&#8221; when they mean search. That is fine in conversation. But once you can see the line, the rest stops being mysterious.</p>
<h2 id="rosetta-stone">The FTP-to-Git Rosetta Stone</h2>
<p>Here is every term you have heard, mapped to something you already do in FileZilla. Read the middle column first, then the right one.</p>
<table class="styled-table">
<thead>
<tr>
<th>Term</th>
<th>What it means in FTP</th>
<th>What it means in Git / GitHub</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Repository (repo)</strong></td>
<td>The root folder on your server where all the site files live.</td>
<td>Your project folder, except it also stores the full history of every file, not just the latest version.</td>
</tr>
<tr>
<td><strong>Clone</strong></td>
<td>Downloading all the files from the server so you can work on them locally.</td>
<td>Downloading a complete copy of the project, including its entire history, not just the current files.</td>
</tr>
<tr>
<td><strong>Commit</strong></td>
<td>You save the file on your computer. Mess it up and the old version is overwritten and gone.</td>
<td>You save with a short note explaining what changed (&#8220;Fixed broken image link&#8221;). The old version stays safe, and you can return to it anytime.</td>
</tr>
<tr>
<td><strong>Push</strong></td>
<td>Dragging your updated files into FileZilla to upload, overwriting what was on the server.</td>
<td>Sending your commits up to the shared copy. It adds them to the history instead of overwriting it.</td>
</tr>
<tr>
<td><strong>Pull</strong></td>
<td>Downloading a file because a colleague just updated it and you need their version.</td>
<td>Fetching everyone&#8217;s latest changes and updating your local copy to match.</td>
</tr>
<tr>
<td><strong>Branch</strong></td>
<td>Making a <code>/public_html_test/</code> folder to try a new homepage without breaking the live site.</td>
<td>An isolated parallel version of the project. You work on it freely, and the main code is untouched until you choose to bring it in.</td>
</tr>
<tr>
<td><strong>Merge</strong></td>
<td>Manually copying files from <code>/public_html_test/</code> over <code>/public_html/</code> once they work.</td>
<td>Combining the work from a branch back into the main project, line by line, mostly automatically.</td>
</tr>
<tr>
<td><strong>Pull request (PR)</strong></td>
<td>Emailing your boss: &#8220;I finished the test folder, can you review before I overwrite the live site?&#8221;</td>
<td>A documented request for someone to review your branch before it gets merged. A GitHub feature, not core Git.</td>
</tr>
</tbody>
</table>
<h2 id="three-things">The three things FTP simply cannot do</h2>
<p>The table makes these look like translations. Three of them are not translations at all. They are things FTP has no answer for, and they are the whole reason version control exists.</p>
<p><strong>You can always go back.</strong> Every commit is a labeled save point. Break the site at 4pm and you can return to how it was at 2pm, with a record of what happened in between. This is the one that changes how it feels to work.</p>
<div class="mac-key-point">
<p>FTP remembers one version of your site: the one on the server right now. Git remembers all of them.</p>
</div>
<p><strong>You can experiment without a test folder.</strong> A branch is the <code>/public_html_test/</code> trick done properly. You spin off a parallel version, try the risky redesign, and the live code is never touched. If it works, you bring it in. If it does not, you throw the branch away and nothing was ever at risk. No more folders named test, test2, test-final, and test-final-REAL.</p>
<p><strong>Someone can review before it goes live.</strong> A <a href="https://docs.github.com/en/pull-requests" target="_blank" rel="noopener noreferrer">pull request</a> is that email to your boss, except it is structured, it shows the exact lines you changed, and the review stays next to the code forever. For anyone who inherits other people&#8217;s codebases for a living, this is the difference between &#8220;trust me, it works&#8221; and &#8220;here is exactly what I changed and why.&#8221;</p>
<h2 id="where-it-breaks">Where the FTP comparison breaks</h2>
<p>No analogy survives contact with the details. Three points are worth knowing before you start, so they do not surprise you later.</p>
<p><strong>Merge is not always automatic.</strong> When two people change the same lines of the same file, Git stops and asks you to resolve the conflict by hand. That feels like a failure the first time. It is the opposite. FTP would have silently let the second upload erase the first. Git refuses to guess when it cannot be sure, which is exactly the behavior you want from a tool that is holding your history.</p>
<p><strong>Push does not overwrite, until you force it.</strong> Normal pushes only add to the history. There is a &#8220;force push&#8221; that rewrites it, and it can undo other people&#8217;s work if you are careless. Treat it the way you treat <code>rm -rf</code>: know it exists, do not reach for it.</p>
<p><strong>The history lives on your machine first.</strong> This is the part FTP people find strangest. Your commits are saved locally, in full, before anything touches a server. You can work on a plane, commit ten times, and push all of it when you land. The server copy is a convenience for sharing, not the source of truth. That inversion is the entire point of a distributed system.</p>
<h2 id="why-wordpress">Why WordPress developers got away without it</h2>
<p>I will be honest about the reason so many capable developers skipped version control for years: for a lot of work, FTP was genuinely enough. One person, one <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> site, a plugin update here, a CSS tweak there. Git felt like ceremony around a job that took ten seconds. That is a fair reason, not laziness, and I am not going to pretend otherwise.</p>
<p>But the reason stops holding the moment the work gets real. The day a second developer touches the same files. The day a client asks what changed on the 14th and whether you can undo just that one thing. The day you inherit a codebase someone else built and need to understand its history before you touch it, which is exactly where version control sits inside a real <a href="https://www.macronimous.com/blog/web-development-life-cycle-process-flow-diagram/">web development process</a>. FTP has no answer to any of those. Version control answers all three without you thinking about it.</p>
<p>There is a newer reason too. Every serious <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> coding tool now assumes Git. Claude Code, GitHub Copilot, and Cursor all lean on version control to show you what they changed and to let you throw it away. A <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a> loop is impossible without it, because the whole safety net is being able to reject what the tool wrote. If you care about <a href="https://www.macronimous.com/blog/writing-clean-code-with-ai/">clean code from AI tools</a>, version control is the floor you build it on.</p>
<p>So here is the opinion, plainly. If you build websites for a living and version control was never part of how you work, that is a gap, not a preference. WordPress made it easy to avoid. It did not make it wise to avoid. For a seasoned developer, keeping the full history of your work should be as automatic as keeping backups.</p>
<h2 id="getting-started">Starting without the terminal fear</h2>
<p>The fear is almost always the terminal, and you can skip it entirely. GitHub Desktop and the built-in Git panel in editors like VS Code give you commit, push, and pull as buttons, with the change history laid out visually. It looks a lot more like FileZilla than like a command line.</p>
<p>Pick one project, ideally a small one, and start there:</p>
<ul class="mac-checklist">
<li>Install GitHub Desktop, or open the Source Control panel in VS Code.</li>
<li>Put one existing project under version control (the tool calls this &#8220;create a repository&#8221;).</li>
<li>Commit whenever you would normally save, and write a one-line note each time.</li>
<li>Push when you would normally upload to the server.</li>
<li>Create a private repository on GitHub as your off-site backup.</li>
</ul>
<p>Do that for two weeks on one project and the vocabulary stops being vocabulary. It becomes muscle memory, the same way dragging a file into FileZilla once did. When you want the full picture, the free <a href="https://git-scm.com/book/en/v2" target="_blank" rel="noopener noreferrer">Pro Git book</a> is the reference worth keeping open.</p>
<p>You are not learning a new discipline. You are finally getting the receipts for the one you already practice.</p>
<div class="mac-cta-box">
<h3>Working with a dev team that keeps the receipts</h3>
<p>We build and maintain sites under proper version control, so every change is documented, reviewable, and reversible. If you are handing a project to an outside team, that history is what protects you.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/custom-web-development/">See how we build</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/github-for-ftp-users-in-the-age-of-agentic-coding/">GitHub for FTP Users in the age of Agentic coding:</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/github-for-ftp-users-in-the-age-of-agentic-coding/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Building a WordPress MCP Server When the Spec Changed</title>
		<link>https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/</link>
					<comments>https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/#respond</comments>
		
		<dc:creator><![CDATA[Benny]]></dc:creator>
		<pubDate>Wed, 29 Jul 2026 05:29:22 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[MCP Development]]></category>
		<category><![CDATA[WordPress Development]]></category>
		<category><![CDATA[MCP Server]]></category>
		<category><![CDATA[Wordpress development]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5282</guid>

					<description><![CDATA[<p>MCP 2026-07-28 is the fifth revision of the Model Context Protocol, and it retires sessions and the initialize handshake in favor of plain request and response. That change suits WordPress unusually well. PHP already treats every request as an independent, shared-nothing process, so a WordPress MCP server no longer has to fake a persistent connection [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/">Building a WordPress MCP Server When the Spec Changed</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></description>
										<content:encoded><![CDATA[<a href="https://www.macronimous.com/blog/wp-content/uploads/2026/07/Building-a-WordPress-MCP-Server-When-the-Spec-Changed.jpg"><img loading="lazy" decoding="async" width="1731" height="909" src="https://www.macronimous.com/blog/wp-content/uploads/2026/07/Building-a-WordPress-MCP-Server-When-the-Spec-Changed.jpg" alt="uilding a WordPress MCP Server When the Spec Changed" class="aligncenter size-full wp-image-5284" /></a>
<div class="mac-direct-answer">
<p><strong>MCP 2026-07-28</strong> is the fifth revision of the Model Context Protocol, and it retires sessions and the <code>initialize</code> handshake in favor of plain request and response. That change suits <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> unusually well. PHP already treats every request as an independent, shared-nothing process, so a WordPress MCP server no longer has to fake a persistent connection it was never going to hold open anyway.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">What&#8217;s on this page</p>
<ul>
<li><a href="#spec-changed">The spec changed the night before we submitted</a></li>
<li><a href="#right-shape">Why WordPress was already the right shape</a></li>
<li><a href="#what-broke">What broke was the handshake, and only the handshake</a></li>
<li><a href="#both-generations">We&#8217;re supporting both generations, not switching</a></li>
<li><a href="#timing">The timing call was harder than the code</a></li>
<li><a href="#cacheable">The small win: tool lists are cacheable now</a></li>
<li><a href="#read-the-rest">The rest of the changelog, for WordPress builders</a></li>
<li><a href="#simple-survives">Simple survives revisions. Clever gets rewritten.</a></li>
</ul>
</div>
<h2 id="spec-changed">The spec changed the night before we submitted</h2>
<p>On July 28, 2026 the Model Context Protocol shipped its fifth revision. We were hours away from uploading our WordPress MCP plugin to the plugin directory.</p>
<p>The headline change was a stateless core. MCP went from a bidirectional, stateful protocol to plain request and response. Sessions gone. The <code>initialize</code> and <code>initialized</code> handshake, retired outright. You can read the <a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/" target="_blank" rel="noopener noreferrer">maintainers&#8217; release notes</a> for the full list, and Anthropic published <a href="https://claude.com/blog/bringing-mcp-2026-07-28-to-claude" target="_blank" rel="noopener noreferrer">its own summary of what&#8217;s rolling out across Claude</a> the same day.</p>
<p>So the first question was not &#8220;should we adopt this.&#8221; It was &#8220;are we broken.&#8221;</p>
<p>Mostly, no. And for a deeply unglamorous reason.</p>
<h2 id="right-shape">Why WordPress was already the right shape</h2>
<p>In the first week of the build we had a choice about how the server replies. The old spec allowed a persistent event stream, or a single JSON response per request. We took the single response. Not because we saw anything coming, but because server-sent events inside PHP, under a shared host&#8217;s process model, is a bad afternoon that turns into a bad week.</p>
<p>The simpler option was permitted. We took it and moved on.</p>
<p>Eight months later the protocol moved to exactly that shape. Every server that had built the sophisticated version now has a migration to do. We had already accidentally arrived.</p>
<p>There is a general point buried in that, and it&#8217;s the reason this release matters beyond one plugin. WordPress has always been shared-nothing. A PHP request boots, does its work, and dies with no memory of the request before it. The previous MCP spec fought that model by asking for a connection WordPress does not naturally hold. The new spec asks for exactly what WordPress already does.</p>
<h2 id="what-broke">What broke was the handshake, and only the handshake</h2>
<p><code>initialize</code> and <code>initialized</code> were the first code written on this project, the thing that proved a WordPress site could act as an MCP server at all. Both are now retired in favor of an optional <code>server/discover</code> call. The most protocol-shaped part of the build was the part that aged worst.</p>
<p>Everything downstream, reading site data and returning JSON, did not care at all.</p>
<div class="mac-key-point">
<p>The parts of our build that broke were the parts doing protocol ceremony. The parts doing the actual job carried on untouched, because they had never known what a session was.</p>
</div>
<p>Here is what a call looks like now. Note that there is nothing to establish first, and nothing stored between requests.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search

{"jsonrpc":"2.0","id":1,"method":"tools/call",
 "params":{"name":"search","arguments":{"q":"otters"},
 "_meta":{"io.modelcontextprotocol/clientInfo":{"name":"my-app","version":"1.0"}}}}</pre><p>&nbsp;</p>
<p>Protocol version, client identity and client capabilities all travel in the request itself. Any request can land on any instance behind an ordinary load balancer. For a plugin running on shared hosting, that is not an abstract scalability win. It removes the one requirement PHP was worst equipped to meet.</p>
<h2 id="both-generations">We&#8217;re supporting both generations, not switching</h2>
<p>The new spec carries a formal deprecation policy with a twelve-month minimum window. That changes the calculation. A plugin installed on somebody else&#8217;s site has to work with whatever client that person happens to have. You do not get to pick.</p>
<p>So version negotiation decides per request, and nothing is stored between them. Two code paths, one endpoint, no session table.</p>
<p>Agencies will recognize the shape of this problem. If you <a href="https://www.macronimous.com/services/cms-development/wordpress-maintenance-services/">maintain WordPress sites for other people</a>, you already live with the gap between the version you&#8217;d like everyone to run and the version they actually run. Protocol support is the same discipline applied one layer down, and it accumulates the same way <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">technical debt accumulates in WordPress</a>: quietly, until a release forces the accounting.</p>
<h2 id="timing">The timing call was harder than the code</h2>
<p>Directory review takes weeks. Submitting on the previous spec would have meant being reviewed, approved and listed as already out of date, in a category where the audience reads specifications for a living.</p>
<p>A day of work against several weeks of looking dated is not a close call.</p>
<p>The engineering question was answered in an hour. The publishing question was the harder one, because the cost of shipping something correct-but-stale is paid entirely in credibility, and you cannot patch that in version 1.0.1.</p>
<h2 id="cacheable">The small win: tool lists are cacheable now</h2>
<p>One change we are quietly pleased about. List responses now carry a time-to-live and a cache scope.</p>
<p>Through the entire build, every time we added a capability, we had to disconnect and reconnect the client, because it had cached a stale catalog with no way to learn it was stale. Minor, constant, mildly maddening. Now the protocol handles it.</p>
<p>Nobody will write a headline about that one. It is the change that will show up most often in daily use.</p>
<h2 id="read-the-rest">The rest of the changelog, for WordPress builders</h2>
<p>If you are building or evaluating anything MCP-shaped on WordPress, these are the items worth reading properly in the <a href="https://modelcontextprotocol.io/specification/2026-07-28/" target="_blank" rel="noopener noreferrer">2026-07-28 specification</a> rather than skimming.</p>
<table class="styled-table">
<thead>
<tr>
<th>What changed</th>
<th>What it means for a WordPress MCP server</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sessions and the <code>initialize</code>/<code>initialized</code> handshake retired</td>
<td>Delete the handshake code. Each request carries its own protocol version and client info in <code>_meta</code>. PHP&#8217;s request model already worked this way.</td>
</tr>
<tr>
<td><code>Mcp-Method</code> and <code>Mcp-Name</code> required as HTTP headers</td>
<td>Your endpoint reads them, and anything in front of you must not strip them. Security plugins and aggressive WAF rules are the thing to test here.</td>
</tr>
<tr>
<td>List responses carry <code>ttlMs</code> and <code>cacheScope</code></td>
<td>Clients stop re-fetching your tool catalog on every connect. Adding a tool no longer means telling users to reconnect.</td>
</tr>
<tr>
<td>Sampling, elicitation and roots replaced by Multi Round-Trip Requests</td>
<td>A tool needing user confirmation returns <code>input_required</code>, and the client retries with the answers attached. No held-open stream, which PHP was never going to hold.</td>
</tr>
<tr>
<td>Dynamic Client Registration deprecated in favor of Client ID Metadata Documents</td>
<td>The long-term path away from <a href="https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/" target="_blank" rel="noopener noreferrer">Application Passwords</a> toward standard OAuth. Real, but not a migration to rush.</td>
</tr>
<tr>
<td>Legacy HTTP+SSE transport deprecated, twelve-month minimum window</td>
<td>If you built on the event stream, you have a year. If you built on single JSON responses, you have nothing to do.</td>
</tr>
</tbody>
</table>
<h2 id="simple-survives">Simple survives revisions. Clever gets rewritten.</h2>
<p>Budget for the specification moving. Not as a line in a risk register, as a genuine expectation.</p>
<p>You cannot predict which way it moves, so insulation is not foresight. What actually protected us was choosing the simplest option the spec permitted, at a moment when the constraint was our own platform rather than any theory about the future. And keeping the protocol layer thin and separate from the work, which is the same discipline we apply to <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a> and to <a href="https://www.macronimous.com/blog/writing-clean-code-with-ai/">keeping AI-generated code clean</a>.</p>
<ul class="mac-checklist">
<li>Take the simplest option the spec permits, especially when your own platform is the constraint</li>
<li>Keep protocol handling in its own layer, separate from the code doing the actual work</li>
<li>Support two spec generations rather than switching, if your code runs on installs you don&#8217;t control</li>
<li>Negotiate the version per request and store nothing between requests</li>
<li>Test that nothing in front of your endpoint strips <code>Mcp-Method</code> or <code>Mcp-Name</code></li>
<li>Read the deprecation policy before planning a migration, not after</li>
</ul>
<p>We got this one right by accident. That is not a method, and I would not want anyone reading this to treat it as one. A hands-on guide to connecting a live WordPress site to Claude is in progress and will follow this post.</p>
<div class="mac-cta-box">
<h3>Thinking about connecting your WordPress site to an AI assistant?</h3>
<p>We build and maintain WordPress for agencies and site owners in the US, UK and Australia, including the plumbing that makes a site readable by <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> tools rather than just by browsers.</p>
<p><a href="https://www.macronimous.com/services/cms-development/wordpress-development-india/" class="mac-cta-button">Talk to our WordPress team</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/">Building a WordPress MCP Server When the Spec Changed</a> first appeared on <a rel="nofollow" href="https://www.macronimous.com/blog">Macronimous Blog</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.macronimous.com/blog/building-a-wordpress-mcp-server-when-the-spec-changed/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
