<?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>Web Development &#8211; Macronimous Blog</title>
	<atom:link href="https://www.macronimous.com/blog/category/web-development/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>Fri, 23 Oct 2026 06:54:03 +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>PHP2Shell: What PHP Developers Must Do After the Host Patches</title>
		<link>https://www.macronimous.com/blog/php2shell-php-developers-checklist/</link>
					<comments>https://www.macronimous.com/blog/php2shell-php-developers-checklist/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Fri, 23 Oct 2026 06:54:03 +0000</pubDate>
				<category><![CDATA[Web Development]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[Magento]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Prestashop security]]></category>
		<category><![CDATA[Website Security]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5452</guid>

					<description><![CDATA[<p>PHP2Shell is the nickname for two critical PHP engine bugs (CVE-2026-109618 and CVE-2026-109619) that let an attacker without a login run code on a PHP server. The fix is a PHP update, not a PrestaShop, Magento or Laravel update. Once your host patches PHP, a handful of app-side checks still belong to your development team, [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/php2shell-php-developers-checklist/">PHP2Shell: What PHP Developers Must Do After the Host Patches</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/10/PHP2Shell.jpg"><img fetchpriority="high" decoding="async" width="1680" height="944" src="https://www.macronimous.com/blog/wp-content/uploads/2026/10/PHP2Shell.jpg" alt="PHP2Shell for PHP developers" class="aligncenter size-full wp-image-5456" /></a>
<div class="mac-direct-answer">
<p><strong>PHP2Shell</strong> is the nickname for two critical PHP engine bugs (CVE-2026-109618 and CVE-2026-109619) that let an attacker without a login run code on a PHP server. The fix is a PHP update, not a <a href="https://www.macronimous.com/blog/prestashop-agentic-commerce-ai-integration-guide/">PrestaShop</a>, Magento or Laravel update. Once your host patches PHP, a handful of app-side checks still belong to your development team, and this post walks through them.</p>
</div>
<p><em>Last updated: October 23, 2026. This story is still moving, and we&#8217;ll revise this post as framework vendors and hosts publish more.</em></p>
<p>Want it on one page for your team or your host? <a href="https://www.macronimous.com/blog/wp-content/uploads/2026/10/php2shell-checklist-macronimous.pdf" target="_blank" rel="noopener" download>Download the PHP2Shell checklist (PDF)</a>. It has every check and command from this post, with tick boxes.</p>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#what-it-is">What PHP2Shell actually is</a></li>
<li><a href="#framework-update">Why your framework update won&#8217;t fix it</a></li>
<li><a href="#steps">The app-side checklist, step by step</a></li>
<li><a href="#prestashop">PrestaShop notes</a></li>
<li><a href="#magento">Magento notes</a></li>
<li><a href="#laravel">Laravel and custom PHP notes</a></li>
<li><a href="#stopgaps">If your host hasn&#8217;t patched yet</a></li>
<li><a href="#who-owns-what">Who owns what</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</div>
<p>Every hosting company spent this week sending the same email: update PHP. That&#8217;s correct, and it&#8217;s also where most of the coverage stops. cPanel shipped patched builds, the security vendors published their notes, and the people who actually maintain the PrestaShop store or the Laravel app were left with a question nobody answered for them. Is the host&#8217;s update the end of it?</p>
<p>No. It&#8217;s the most important part, but it isn&#8217;t the end. A few of the conditions these bugs rely on live in your application&#8217;s configuration and code, not in the host&#8217;s PHP package, and the host won&#8217;t touch them (they can&#8217;t, really, because they don&#8217;t know your app).</p>
<h2 id="what-it-is">What PHP2Shell actually is</h2>
<p>On October 21, 2026, the PHP project released 8.2.35, 8.3.36, 8.4.27 and 8.5.12 to fix two bugs rated Critical, with a CVSS (Common Vulnerability Scoring System) v4.0 score of 9.2. Both allow remote code execution, which means an attacker can make your server run their commands.</p>
<p>The first, <a href="https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w" target="_blank" rel="noopener noreferrer">CVE-2026-109618</a>, is about running out of memory at a bad moment. If PHP hits its memory limit while it&#8217;s still setting up a request, it leaves half-built data behind, and the shutdown code later trusts that data. On Apache mod_php this all happens before your application code runs at all.</p>
<p>The second, <a href="https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp" target="_blank" rel="noopener noreferrer">CVE-2026-109619</a>, is about state leaking between requests. Something from one request survives on the worker after its memory is freed, and the next request on that same worker uses it. That second request can be triggered by something as mundane as a PHP warning from a deprecated setting.</p>
<p>The timing made this worse than usual. According to <a href="https://sansec.io/research/php2shell" target="_blank" rel="noopener noreferrer">Sansec&#8217;s timeline</a>, details leaked on October 20, and Cloudflare saw early attacks the next morning, hours before the fixed versions were out. The vulnerable code goes back to PHP 5.4.0, released in 2012, so nearly every PHP site you&#8217;ve ever worked on was in scope.</p>
<h2 id="framework-update">Why your framework update won&#8217;t fix it</h2>
<p>This is the part clients get wrong, and honestly some developers too. The bugs are in the PHP engine. PrestaShop, Magento and Laravel all run on top of that engine, so a <code>composer update</code> or a new module version changes nothing here. Sansec put it plainly for <a href="https://www.macronimous.com/blog/shopify-plus-seo-technical-playbook/">eCommerce</a>: Magento stores are affected even when their own code is clean.</p>
<p>As of today, we haven&#8217;t found an advisory from PrestaShop, Adobe or the Laravel team about these CVEs. So the stack-specific notes further down are our own practitioner guidance, not quotes from the vendors. If that changes, we&#8217;ll update this post and say so.</p>
<div class="mac-key-point">
<p>The host patches PHP. Your team owns the configuration and code paths that decide how easy the next bug in this family will be to exploit.</p>
</div>
<h2 id="steps">The app-side checklist, step by step</h2>
<p>These steps assume you have SSH (Secure Shell, the remote terminal) access to the server or at least to the site&#8217;s files. Paths in uppercase are placeholders. Replace SITE_ROOT with your site&#8217;s document root, for example <code>&#47;home&#47;USER&#47;public_html</code> on cPanel hosting.</p>
<h3>Step 1: Confirm the patched PHP is really running</h3>
<p>The version number alone can mislead you. cPanel backported the fix into older branches (ea-php74 through ea-php81), so a server that still reports PHP 8.1 may already be patched. Upstream PHP has no fix for 8.1 and older, which is why the changelog matters more than the number. On a cPanel server running RHEL or AlmaLinux, run this, replacing php81 with the version the site uses:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">rpm -q --changelog ea-php81-php-cli | grep -m1 CVE-2026-109618</pre><p>&nbsp;</p>
<p>If it prints a changelog line that mentions CVE-2026-109618, that PHP build has the fix. If it prints nothing, the build isn&#8217;t patched yet. For PHP older than 7.4, cPanel points to TuxCare ELS (Extended Lifecycle Support), a paid service that keeps backporting security fixes.</p>
<h3>Step 2: Find out which SAPI serves the site</h3>
<p>The SAPI (Server API) is the way PHP connects to the web server, and it changes your risk a lot. Apache mod_php is the worst case, because both bugs were proven against it with default settings. PHP-FPM (FastCGI Process Manager) is harder to attack, but one confirmed attack path works against it too, so don&#8217;t treat it as safe. Create a temporary file in the site root with this content:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?php echo PHP_VERSION . ' ' . PHP_SAPI;</pre><p>&nbsp;</p>
<p>Open it in a browser. You&#8217;ll see something like <code>8.3.36 fpm-fcgi</code> or <code>8.1.34 apache2handler</code>. The second word is the SAPI, and <code>apache2handler</code> means mod_php. Use a random file name, and delete the file the moment you&#8217;ve read it.</p>
<p>The command line can report a different PHP from the one serving web requests, which is why this check goes through the browser and not <code>php -v</code>. (On shared hosts, those two disagree more often than you&#8217;d think.)</p>
<h3>Step 3: Clean up per-directory PHP settings</h3>
<p>This is the step only your team can do, and it&#8217;s the one I&#8217;d put first if I could only pick one. On mod_php, any <code>php_value</code> or <code>php_flag</code> line in an <code>.htaccess</code> file is applied before the request starts. If that value is deprecated or invalid, PHP raises a warning at that early stage, and the second bug uses exactly that kind of early warning. Hiding the warning doesn&#8217;t help, because the advisory says the damage happens before PHP decides whether to display or log it. List every such line in the site:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">grep -rn --include=.htaccess -E "php_(value|flag|admin_value|admin_flag)" SITE_ROOT</pre><p>&nbsp;</p>
<p>Remove the obsolete ones and move the rest into <code>php.ini</code> or the FPM pool configuration. The advisory names these as typical offenders:</p>
<ul>
<li>The mbstring encoding settings <code>mbstring.internal_encoding</code>, <code>mbstring.http_input</code> and <code>mbstring.http_output</code> are a problem whenever they&#8217;re set explicitly.</li>
<li>The iconv settings <code>iconv.input_encoding</code>, <code>iconv.output_encoding</code> and <code>iconv.internal_encoding</code> are a problem in the same way.</li>
<li>The setting <code>auto_detect_line_endings=1</code> raises a deprecation warning on current PHP.</li>
<li>Malformed values such as a <code>memory_limit</code> written with a typo raise warnings on PHP 8.2 and later.</li>
</ul>
<p>On nginx with PHP-FPM, the same risk applies when the server passes <code>PHP_VALUE</code> or <code>PHP_ADMIN_VALUE</code> to PHP. Check the nginx configuration too:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">grep -rn -E "PHP_VALUE|PHP_ADMIN_VALUE" &#47;etc&#47;nginx&#47;</pre><p>&nbsp;</p>
<h3>Step 4: Turn off session upload progress</h3>
<p>Session upload progress is a PHP feature that reports how far a file upload has got. It&#8217;s on by default, almost nothing uses it, and it&#8217;s the path behind one of the confirmed attacks on both mod_php and FPM. PHP&#8217;s own advisory suggests keeping it off even after you patch, because attackers will keep poking at it. Check whether your code uses it first:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">grep -rn "upload_progress" SITE_ROOT --include=*.php</pre><p>&nbsp;</p>
<p>If that finds nothing outside vendor libraries, add this line to <code>php.ini</code> or the FPM pool configuration:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">session.upload_progress.enabled = 0</pre><p>&nbsp;</p>
<p>Restart PHP so every worker picks it up. On a typical Ubuntu server with PHP 8.3, the command is below; on cPanel, restart PHP-FPM from WHM instead.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">sudo systemctl restart php8.3-fpm</pre><p>&nbsp;</p>
<h3>Step 5: Hunt the fatal errors an attacker can trigger</h3>
<p>The second bug needs a request to die halfway through something, and a fatal error is how that happens. Out-of-memory is the obvious one, but the advisory also counts uncaught errors, failed <code>require</code> calls and &#8220;Maximum execution time exceeded.&#8221; It specifically asks teams to look at upload, import, error page, shutdown, destructor, serialization and extension paths, not just the pages you browse every day. Pull the most frequent fatals from the PHP error log:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">grep -oE "PHP Fatal error: .{0,120}" SITE_ROOT/error_log | sort | uniq -c | sort -rn | head -30</pre><p>&nbsp;</p>
<p>Any fatal that an outside visitor can cause on purpose (a huge CSV import, an oversized image, a malformed upload) is worth fixing now, patch or no patch. A clean log isn&#8217;t proof, though. Old logs often skipped deprecation notices, and a log only covers the routes people actually hit.</p>
<h3>Step 6: Stop oversized requests before PHP sees them</h3>
<p>The first bug&#8217;s memory exhaustion tricks rely on multipart requests (the format forms use to send files) that look small but expand inside PHP. Keep <code>post_max_size</code> conservative, lower <code>max_input_nesting_level</code>, and set sensible caps on <code>max_input_vars</code> and <code>max_file_uploads</code>. Better still, if a route never accepts file uploads, refuse multipart requests at the web server. On nginx, for a JSON-only API path:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">location /api/ {
    if ($http_content_type ~* "multipart/form-data") {
        return 415;
    }
    try_files $uri $uri/ /index.php?$query_string;
}</pre><p>&nbsp;</p>
<p>On Apache 2.4, the same rule looks like this in the virtual host or the root <code>.htaccess</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;If "%{REQUEST_URI} =~ m#^/api/# &amp;&amp; %{HTTP:Content-Type} =~ /multipart\/form-data/i"&gt;
    Require all denied
&lt;/If&gt;</pre><p>&nbsp;</p>
<p>After the change, send a test upload to that route. You should get a 415 or 403 response, and your normal JSON requests should still work.</p>
<h3>Step 7: Check for signs of compromise</h3>
<p>Exploitation started before the patch existed, and watchTowr lists both CVEs as exploited in the wild. So check, even if your host patched on day one. Start with PHP files that changed since the leak:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">find SITE_ROOT -name "*.php" -type f -newermt "2026-10-20"</pre><p>&nbsp;</p>
<p>Your own deployments will show up here too, so compare the list against your Git history. Anything you can&#8217;t explain, especially in upload folders or cache folders, needs a closer look. Then search the access logs for the public scanner, which <a href="https://blog.imunify360.com/php-cve-2026-109618-cve-2026-109619/" target="_blank" rel="noopener noreferrer">Imunify360 reports</a> sends a User-Agent starting with <code>php2shell-</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">grep -i "php2shell-" &#47;var&#47;log&#47;apache2&#47;access.log</pre><p>&nbsp;</p>
<p>A hit means someone scanned you, not that they got in. Also check the admin user list in your store or app for accounts nobody remembers creating. A clean result is reassuring, but I wouldn&#8217;t call it proof, and I don&#8217;t think anyone honest would.</p>
<h2 id="prestashop">PrestaShop notes</h2>
<p>A lot of PrestaShop stores live on shared cPanel hosting under Apache, which often means mod_php or LiteSpeed&#8217;s LSAPI. Those are the more exposed setups, so Steps 2 and 3 matter more here than anywhere else.</p>
<p>Old 1.6 and 1.7 stores tend to carry <code>php_value</code> lines in <code>.htaccess</code> that someone added years ago to make an old module work, and nobody&#8217;s looked at them since. Those are exactly what Step 3 is hunting for. The product CSV import and the module upload screen are the places where a big upload can push PHP into memory exhaustion, so put them at the top of your Step 5 list.</p>
<p>The back office needs a high <code>max_input_vars</code> for product and module forms, and that&#8217;s fine. Just don&#8217;t loosen every other input limit to match it; let the request size limits in Step 6 do their job on the storefront.</p>
<p>And the uncomfortable one: PrestaShop 1.6 stores frequently still run PHP 7.x. Those stores are now relying on a host backport or TuxCare to be patched at all. If that&#8217;s you, this is the week to put the upgrade on the calendar, and our <a href="https://www.macronimous.com/services/ecommerce-development/prestashop-development-agency/">PrestaShop development team</a> can plan it with you. If you&#8217;re already on 9.x, our post on <a href="https://www.macronimous.com/blog/prestashop-agentic-commerce-ai-integration-guide/">what changed in PrestaShop 9.x</a> covers the newer architecture.</p>
<h2 id="magento">Magento notes</h2>
<p>Magento (Adobe Commerce) stores tend to run with a very high <code>memory_limit</code>, often 2G or more, and it&#8217;s a common assumption that this protects them. It doesn&#8217;t. The attack works by pushing a request into whatever finite limit is set. A higher limit makes the attacker work a bit harder; it doesn&#8217;t remove the condition. PHP&#8217;s own temporary workaround is to remove the limit entirely (more on why that&#8217;s a bad idea below), which tells you the limit&#8217;s size isn&#8217;t the point.</p>
<p>Catalog and customer CSV imports, and the admin&#8217;s upload paths, belong in Step 5. Stores on older releases have a version problem as well. According to <a href="https://experienceleague.adobe.com/en/docs/commerce-operations/installation-guide/system-requirements" target="_blank" rel="noopener noreferrer">Adobe&#8217;s system requirements</a>, the 2.4.4 and 2.4.5 lines were tested on PHP 8.1 only, and PHP 8.1 gets no upstream fix. Those stores depend on the host&#8217;s backported build. Our <a href="https://www.macronimous.com/services/ecommerce-development/magento-maintenance-support/">Magento maintenance team</a> can check that for you if you&#8217;re not sure which build you&#8217;re on.</p>
<h2 id="laravel">Laravel and custom PHP notes</h2>
<p>Laravel apps that ship as Docker images have a trap of their own. Your host&#8217;s PHP update does nothing for a container. Any image built from a <code>php:8.x</code> base before October 21 still has the vulnerable PHP inside it until you rebuild with a fresh base. The <code>"php"</code> line in <code>composer.json</code> doesn&#8217;t protect you either; it only checks the version number. Rebuild and pull the new base image explicitly:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">docker build --pull --no-cache -t IMAGE_NAME .</pre><p>&nbsp;</p>
<p>Then run Step 2 against the running container to confirm the new version is live. If you use FrankenPHP, note that PHP&#8217;s advisory says it likely behaves like mod_php here, so treat it as the higher-risk case. We wrote about <a href="https://www.macronimous.com/blog/frankenphp-for-modern-php-development/">FrankenPHP for modern PHP development</a> last year, and this is the first time that choice has carried a security footnote.</p>
<p>JSON-only Laravel APIs are the easiest win in this whole post: block multipart on the API routes (Step 6) and you&#8217;ve closed off the request type both bugs lean on. Laravel Excel imports and image processing are the usual memory hogs, so they go on the Step 5 list. If your app is a version or two behind, our <a href="https://www.macronimous.com/blog/upgrade-laravel-application/">Laravel 12 upgrade guide</a> is a reasonable next read, and our <a href="https://www.macronimous.com/services/outsource-php-development/laravel-development-company/">Laravel developers</a> can take that work on.</p>
<p>Custom PHP apps, the ones built in-house ten years ago and kept alive since, are where I&#8217;d expect the most trouble. They&#8217;re the most likely to have stale <code>.htaccess</code> directives and the least likely to have any upload limits at all. If one of them has a custom upload progress bar, that&#8217;s the one legitimate user of the feature in Step 4, so test it before you switch the setting off.</p>
<h2 id="stopgaps">If your host hasn&#8217;t patched yet</h2>
<p>Push the host first. While you wait, PHP&#8217;s advisories list a few temporary settings. None of them is a complete fix, and the advisories say so plainly.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">session.upload_progress.enabled = 0
fatal_error_backtraces = 0</pre><p>&nbsp;</p>
<p>The second line only exists on PHP 8.5, where it removes the producer behind the strongest confirmed exploit. The advisory also lists <code>memory_limit = -1</code> as the main stopgap for the first bug, but I&#8217;d only use it for a day or two on a server you&#8217;re watching closely. It lets a single request eat all the memory on the box, which trades one problem for a different outage. Restart every PHP worker after any change, or the old settings keep running.</p>
<h2 id="who-owns-what">Who owns what</h2>
<table class="styled-table">
<thead>
<tr>
<th>Task</th>
<th>Who owns it</th>
<th>Where it applies</th>
</tr>
</thead>
<tbody>
<tr>
<td>The host installs the patched PHP build.</td>
<td>The host owns this task.</td>
<td>It applies to every stack.</td>
</tr>
<tr>
<td>The team confirms the patch from the changelog, not the version number.</td>
<td>The dev team owns this task.</td>
<td>It matters most on PHP 8.1 and older.</td>
</tr>
<tr>
<td>The team rebuilds container images on a patched base.</td>
<td>The dev team owns this task.</td>
<td>It applies to Dockerized Laravel and custom apps.</td>
</tr>
<tr>
<td>The team removes stale php_value and PHP_VALUE overrides.</td>
<td>The dev team owns this task.</td>
<td>It matters most for PrestaShop and legacy custom apps on Apache.</td>
</tr>
<tr>
<td>The team turns off session upload progress.</td>
<td>The host and the dev team share this task.</td>
<td>It applies to every stack.</td>
</tr>
<tr>
<td>The team fixes fatal errors in import and upload paths.</td>
<td>The dev team owns this task.</td>
<td>It matters most for Magento, PrestaShop and Laravel imports.</td>
</tr>
<tr>
<td>The team blocks multipart requests on routes that don&#8217;t need them.</td>
<td>The dev team owns this task.</td>
<td>It matters most for Laravel APIs.</td>
</tr>
<tr>
<td>The team checks files, admin users and logs for compromise.</td>
<td>The dev team owns this task.</td>
<td>It applies to every stack.</td>
</tr>
</tbody>
</table>
<h3>The short checklist</h3>
<ul class="mac-checklist">
<li>The package changelog on every server shows CVE-2026-109618.</li>
<li>Every Docker image has been rebuilt on a PHP base released after October 21.</li>
<li>You know whether each site runs mod_php, FPM, LSAPI or FrankenPHP.</li>
<li>No .htaccess file or nginx config sets a deprecated or invalid PHP value.</li>
<li>Session upload progress is off, and PHP has been restarted.</li>
<li>The top fatal errors in the error log have been reviewed and fixed.</li>
<li>Routes that never take uploads now refuse multipart requests.</li>
<li>New PHP files since October 20 match your deployment history.</li>
<li>The admin user list holds no accounts nobody can explain.</li>
</ul>
<p>The same list, with the commands, is in the <a href="https://www.macronimous.com/blog/wp-content/uploads/2026/10/php2shell-checklist-macronimous.pdf" target="_blank" rel="noopener" download>printable PHP2Shell checklist (PDF)</a>.</p>
<h2 id="faq">FAQ</h2>
<div id="php2shell-faq">
<div class="faq-item">
<h3>Does updating PrestaShop, Magento or Laravel fix PHP2Shell?</h3>
<p>No. The bugs are in the PHP engine itself, so only a patched PHP build fixes them. Framework and module updates don&#8217;t change PHP.</p>
</div>
<div class="faq-item">
<h3>My host says the server still runs PHP 8.1. Am I vulnerable?</h3>
<p>It depends on the build. Upstream PHP has no fix for 8.1, but cPanel shipped backported builds that keep the old version number. Check the package changelog for CVE-2026-109618, as shown in Step 1.</p>
</div>
<div class="faq-item">
<h3>Is PHP-FPM safe from PHP2Shell?</h3>
<p>No. FPM is harder to attack than Apache mod_php, but PHP confirmed one attack path against FPM through session upload progress. Patch FPM servers with the same urgency.</p>
</div>
<div class="faq-item">
<h3>Do I need session upload progress?</h3>
<p>Almost certainly not. Very few applications use it. Turn it off with session.upload_progress.enabled = 0 unless you have a custom upload progress bar that depends on it.</p>
</div>
<div class="faq-item">
<h3>Will a high memory_limit protect my Magento store?</h3>
<p>No. The attack drives a request into whatever finite memory limit is set. A higher limit raises the effort a little, but it doesn&#8217;t remove the condition.</p>
</div>
<div class="faq-item">
<h3>How do I know if my site was already attacked?</h3>
<p>Look for PHP files changed since October 20 that your deployments don&#8217;t explain, unexpected admin users, and php2shell- User-Agents in the access logs. A clean result is reassuring, but it isn&#8217;t proof.</p>
</div>
</div>
<h2 id="bottom-line">Where this leaves you</h2>
<p>If you take one thing from this: the host&#8217;s patch closes these two bugs, and your team&#8217;s cleanup makes the next one in this family harder to use. I&#8217;d expect more of them, because researchers now know exactly where to look, and the leak showed how fast attackers move once details are out. That&#8217;s not a prediction I can back with a number. It&#8217;s just how the last few PHP-wide incidents have gone.</p>
<p>We&#8217;ll update this post as PrestaShop, Adobe or the Laravel team publish anything. If you&#8217;ve seen something in your own logs that belongs here, tell us and we&#8217;ll add it with credit.</p>
<div class="mac-cta-box">
<h3>Want a second pair of eyes on your PHP2Shell cleanup?</h3>
<p>We check the app-side items your host doesn&#8217;t touch, on PrestaShop, Magento, Laravel and custom PHP sites, and tell you plainly what we found.</p>
<p><a href="https://www.macronimous.com/contact-us/" class="mac-cta-button">Book a PHP2Shell Check</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/php2shell-php-developers-checklist/">PHP2Shell: What PHP Developers Must Do After the Host Patches</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/php2shell-php-developers-checklist/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spec-Driven Development: How We Use AI Without Gambling Your Budget</title>
		<link>https://www.macronimous.com/blog/spec-driven-development-with-ai/</link>
					<comments>https://www.macronimous.com/blog/spec-driven-development-with-ai/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Wed, 21 Oct 2026 11:27:30 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[AI Development]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[acceptance tests]]></category>
		<category><![CDATA[AI app development]]></category>
		<category><![CDATA[AI-assisted development]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[spec-driven development]]></category>
		<category><![CDATA[Vibe coding]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5425</guid>

					<description><![CDATA[<p>Spec-driven development with AI means you write the specification and the acceptance tests first, then let AI coding tools build against that document while an engineer checks every change. Vibe coding gets you a demo. A spec gets you a product someone is accountable for. For a paying client app, the spec is also what [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/spec-driven-development-with-ai/">Spec-Driven Development: How We Use AI Without Gambling Your Budget</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/10/Spec-Driven-Development-scaled.jpg"><img decoding="async" width="2560" height="1352" src="https://www.macronimous.com/blog/wp-content/uploads/2026/10/Spec-Driven-Development-scaled.jpg" alt="Spec-Driven Development in the age of AI" class="aligncenter size-full wp-image-5433" /></a>
<div class="mac-direct-answer">
<p><strong>Spec-driven development with <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a></strong> means you write the specification and the acceptance tests first, then let <a href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">AI coding</a> tools build against that document while an engineer checks every change. <a href="https://www.macronimous.com/blog/is-vibe-coding-bad/">Vibe coding</a> gets you a demo. A spec gets you a product someone is accountable for. For a paying client app, the spec is also what keeps the budget from drifting.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">What&#8217;s in this post</p>
<ul>
<li><a href="#demo-worked">The demo worked. Then somebody had to own it.</a></li>
<li><a href="#what-a-spec-is">What we mean by a spec</a></li>
<li><a href="#acceptance-tests">Acceptance tests are where &#8220;done&#8221; gets written down</a></li>
<li><a href="#how-we-build">How a spec-driven build runs at Macronimous</a></li>
<li><a href="#cost">Why the spec is the real cost saver</a></li>
<li><a href="#vibe-coding-wins">Where vibe coding still wins</a></li>
<li><a href="#comparison">Vibe coding vs spec-driven development</a></li>
<li><a href="#checklist">Is your spec ready? A short checklist</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</div>
<h2 id="demo-worked">The demo worked. Then somebody had to own it.</h2>
<p>If you&#8217;ve been near software this year, you&#8217;ve seen one. Somebody opens a laptop, types a few sentences into an AI tool, and twenty minutes later there&#8217;s an app on the screen. It has a login page. It has a dashboard. The buttons do things. Everyone in the room is impressed, and honestly, they should be.</p>
<p>Then the questions start. What happens when two people edit the same record? Who decided the chatbot should answer pricing questions? Which version of this is the one we&#8217;re paying for? And nobody can answer, because there&#8217;s nothing to check the app against. There&#8217;s a chat history, and the chat history is the only record of what anyone wanted.</p>
<p>That&#8217;s the gap this post is about. We&#8217;ve built software for clients since 2002, and we build with AI every day now. What we won&#8217;t do is build a client&#8217;s product without a written spec. Here&#8217;s what that spec looks like, and why it&#8217;s the part of the project that saves you money.</p>
<h2 id="what-a-spec-is">What we mean by a spec</h2>
<p>Two definitions first, in plain words, because both terms get stretched.</p>
<p><strong>Vibe coding</strong> is prompting an AI until something seems to work, with no written plan and very little reading of the code it produces. Andrej Karpathy coined the term in February 2025, and the <a href="https://glossary.openssf.org/vibe-coding/" target="_blank" rel="noopener noreferrer">OpenSSF security glossary</a> puts the line in a sensible place: if a human reviews the AI-generated code, it isn&#8217;t vibe coding anymore.</p>
<p><strong>Spec-driven development</strong> is the opposite order. You write down what the software must do, and how you&#8217;ll prove it does it, before anyone (or anything) writes code. The AI then builds against that document. GitHub thinks the idea is solid enough that it published <a href="https://github.com/github/spec-kit" target="_blank" rel="noopener noreferrer">Spec Kit</a>, an open-source toolkit built around exactly this flow: specify, plan, break into tasks, implement.</p>
<p>A spec isn&#8217;t an 80-page requirements binder (we&#8217;ve written those, in the 2000s, and nobody read them). Ours are short, plain documents that cover features, user flows, the data model, the integrations, how any AI inside the product should behave, and the acceptance tests. Here&#8217;s a trimmed section from the kind of spec we&#8217;d write for a support assistant on a store. It&#8217;s an illustration, not a client document.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">## Feature: Order-status assistant

Purpose
The assistant answers order-status questions for signed-in customers.

Data it can read
- Orders for the signed-in customer only.
- The published shipping and returns policy pages.

Data it must not read
- Other customers' orders.
- Internal staff notes on any order.

AI behavior rules
1. Answer only from the order record and the policy pages.
2. If the answer is not in those sources, say so and offer a human handoff.
3. Never quote a refund amount. Link to the refund policy instead.
4. Never promise a delivery date that the carrier has not provided.

Done means
All acceptance tests AT-01 to AT-06 pass.</pre><p>&nbsp;</p>
<p>Notice what&#8217;s in there. Two rules say what the AI must <em>not</em> do. That&#8217;s the part a prompt almost never contains, because nobody thinks to ask for it until a customer gets a refund figure the chatbot made up.</p>
<h2 id="acceptance-tests">Acceptance tests are where &#8220;done&#8221; gets written down</h2>
<p>Most arguments on software projects aren&#8217;t about code. They&#8217;re about whether something is finished. The client thinks the feature is half done, the developer thinks it shipped last Tuesday, and both are right by their own definition.</p>
<p>Acceptance tests end that argument before it starts. Each one states a situation, an action, and the result you&#8217;ll accept. We write them in plain language first, so the client can read and sign them, and then engineers turn them into automated tests where it makes sense.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">AT-01  Given a signed-in customer with order #1042 marked "Shipped",
       when the customer asks "Where is my order?",
       then the assistant states "Shipped" and shows the carrier tracking link.

AT-03  Given a signed-in customer,
       when the customer asks about order #2077, which belongs to another customer,
       then the assistant refuses and reveals no detail of that order.

AT-05  Given a customer who asks "How much will my refund be?",
       when the assistant replies,
       then the reply contains no amount and links to the refund policy page.

AT-06  Given a question the sources cannot answer,
       when the assistant replies,
       then it says it does not know and offers a human handoff.</pre><p>&nbsp;</p>
<p>AT-03, AT-05 and AT-06 are wrong-answer tests. They check what the AI does when it shouldn&#8217;t answer. A demo never exercises those cases, because a demo is a sequence of questions somebody already knows will work. A product gets asked everything.</p>
<h2 id="how-we-build">How a spec-driven build runs at Macronimous</h2>
<p>This is the order every client build follows here, whether it&#8217;s a new AI app, an AI feature inside an existing Magento or <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> store, or one of the <a href="https://www.macronimous.com/services/ai-development/ai-automation-agency/">workflow automations we build</a> for operations teams.</p>
<ol>
<li>The client brings what they have. That can be a finished spec, a requirements document, a page of notes, or an idea explained on a call.</li>
<li>We turn it into a build-ready spec. If the client sent one, we review it and send back a gap list. Client specs usually arrive without acceptance tests or rules for AI behavior.</li>
<li>Senior engineers build against the spec with AI coding tools, Claude Code among them. The spec decides what gets built. The tool doesn&#8217;t.</li>
<li>An engineer reads every AI-written change and runs it against the acceptance tests before it goes in.</li>
<li>The spec stays with the client, in the client&#8217;s repository, as a working document.</li>
</ol>
<p>That last step matters more than it looks. When you add a phase next year, or move the work to another team (it happens, and we&#8217;d rather you could), the next people start from facts. They don&#8217;t start from a chat log and a guess.</p>
<p>I hold myself to the same order. I built <a href="https://thetamilbible.com/" target="_blank" rel="noopener noreferrer">TheTamilBible.com</a>, a PHP and MySQL web app you can install like a phone app, with Claude Code. It started as a written spec, not a prompt: navigation from Testament down to verse, search, light and dark themes, and offline reading. The AI wrote most of the code. The spec is why the result is the app I meant to build and not the app the AI guessed I wanted.</p>
<p>We&#8217;ve written separately about the review discipline behind step four, which we call <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a>.</p>
<h2 id="cost">Why the spec is the real cost saver</h2>
<p>AI made code cheap. That&#8217;s real, and anyone who tells you otherwise is protecting an hourly rate. A screen that took two days now takes an afternoon.</p>
<p>But cheap code moves the expensive mistake somewhere else. The costly thing used to be writing the code. Now it&#8217;s building the wrong thing quickly, discovering it in month three, and paying to unpick features that three other features already depend on. Speed doesn&#8217;t help you there. It just gets you to the wrong place sooner.</p>
<div class="mac-key-point">
<p>When code is cheap, the expensive mistake is building the wrong thing fast. The spec is how you find that mistake while it still costs a conversation instead of a rebuild.</p>
</div>
<p>The spec also changes how you pay. Once it&#8217;s locked, we can send a fixed-price proposal priced line by line against it, so the scope and the price live in the same document. Without a spec, a fixed price is a guess dressed up as a commitment, and somebody pays for the guess later. Usually the client.</p>
<p>Security is the other cost that hides until it doesn&#8217;t. Veracode&#8217;s <a href="https://www.veracode.com/news/llms-are-getting-smarter-but-not-safer-veracode-2026-genai-code-security-report-finds-ai-generated-code-security-has-stalled-at-56%25-pass-rate/" target="_blank" rel="noopener noreferrer">2026 GenAI Code Security Report</a> found the models it tested passed its security tests 56 percent of the time on average, barely better than a year earlier. I don&#8217;t read that as &#8220;AI writes insecure code, so avoid it.&#8221; I read it as: unreviewed AI code needs something to be checked against. A spec with access rules written into it, like AT-03 above, is that something.</p>
<h2 id="vibe-coding-wins">Where vibe coding still wins</h2>
<p>None of this means vibe coding is wrong. I vibe code myself, and I&#8217;d be lying if I said otherwise. It&#8217;s the right call when:</p>
<ul>
<li>You&#8217;re testing whether an idea deserves a budget at all. A rough prototype answers that faster than any meeting.</li>
<li>You&#8217;re building an internal tool that a handful of people use, that touches no money, and that nobody will mind rebuilding.</li>
<li>You want to show a developer or an investor what you mean, in place of describing it.</li>
<li>You&#8217;re learning. Breaking things you built yourself is still the fastest way to understand them.</li>
</ul>
<p>The line is simple. Once the software handles real customers, real money, personal data, or anything a regulator cares about, it needs a spec. We wrote up where that line fell on one of our own builds in <a href="https://www.macronimous.com/blog/is-vibe-coding-bad/">&#8220;Is Vibe Coding Bad?&#8221;</a>, if you want the longer version.</p>
<p>And a vibe-coded prototype is a fine place to start a spec. It&#8217;s a sketch. We&#8217;ll happily write the spec from it.</p>
<h2 id="comparison">Vibe coding vs spec-driven development</h2>
<table class="styled-table">
<thead>
<tr>
<th>Question</th>
<th>Vibe coding</th>
<th>Spec-driven development</th>
</tr>
</thead>
<tbody>
<tr>
<td>What is the build checked against?</td>
<td>The builder checks whether it seems to work.</td>
<td>The engineer checks it against written acceptance tests.</td>
</tr>
<tr>
<td>Who defines &#8220;done&#8221;?</td>
<td>Nobody writes it down.</td>
<td>The client signs the tests that define it.</td>
</tr>
<tr>
<td>What happens to AI behavior rules?</td>
<td>They exist only if someone remembered to prompt them.</td>
<td>The spec lists them, including what the AI must refuse.</td>
</tr>
<tr>
<td>Can the price be fixed?</td>
<td>A fixed price is a guess.</td>
<td>The price is set line by line against the locked spec.</td>
</tr>
<tr>
<td>What does a new team inherit?</td>
<td>The new team inherits code and a chat history.</td>
<td>The new team inherits code and the document that explains it.</td>
</tr>
<tr>
<td>Where does it fit best?</td>
<td>It fits prototypes, experiments and small internal tools.</td>
<td>It fits products that real customers and real money depend on.</td>
</tr>
</tbody>
</table>
<h2 id="checklist">Is your spec ready? A short checklist</h2>
<p>If you already have a spec, run it against this list before you send it to any team, us or anyone else.</p>
<ul class="mac-checklist">
<li>Every feature names the user who uses it and what that user is trying to do.</li>
<li>The data model lists each record type and who can read or change it.</li>
<li>Every integration says what happens when the other system is down or slow.</li>
<li>Any AI feature lists its sources, and what it must refuse to answer.</li>
<li>Each feature has at least one acceptance test written as given, when, then.</li>
<li>At least one test per AI feature checks a wrong or unanswerable question.</li>
<li>The spec states what is out of scope for this phase.</li>
</ul>
<h2 id="faq">FAQ</h2>
<div id="ai-wordpress-faq">
<div class="faq-item">
<h3>What is spec-driven development with AI?</h3>
<p>It&#8217;s a way of building software where the specification and acceptance tests come first, and AI coding tools build against them. An engineer reviews each AI-written change and runs it against the tests before it goes in. The spec stays with the client as the record of what the product must do.</p>
</div>
<div class="faq-item">
<h3>What is the difference between vibe coding and spec-driven development?</h3>
<p>Vibe coding prompts an AI until something seems to work, with no written plan to check it against. Spec-driven development writes the plan and the tests first. Vibe coding is good for prototypes and experiments. Spec-driven development is what a product needs once customers or money depend on it.</p>
</div>
<div class="faq-item">
<h3>Does writing a spec slow the project down?</h3>
<p>It adds days at the start and saves weeks later. AI tools build fast once they have clear instructions, and the spec is those instructions. The time you lose without one goes into rework, scope arguments and features nobody asked for.</p>
</div>
<div class="faq-item">
<h3>Can you build from a spec I already have?</h3>
<p>Yes. We read it first and send back a gap list, which usually covers missing acceptance tests and unclear AI behavior. Once you approve the fixes, the spec is locked and we send a fixed-price proposal. If you only have an idea, our Specification Building service writes the spec with you, and you own it outright.</p>
</div>
</div>
<h2 id="bottom-line">Where that leaves you</h2>
<p>Use AI to write the code. We do, every day. Just don&#8217;t let it decide what the code is for. That decision belongs in a document a person signed, and the document belongs to you. If you&#8217;ve run a spec-driven build with a different structure that worked better, I&#8217;d like to hear about it.</p>
<div class="mac-cta-box">
<h3>Have an idea or a draft spec for an AI app?</h3>
<p>Send what you have. We&#8217;ll tell you what&#8217;s missing from it, whether AI is the right tool, and how we&#8217;d price the build once the spec is locked.</p>
<p><a href="https://www.macronimous.com/services/ai-development/" class="mac-cta-button">Send Us Your Spec or Idea</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/spec-driven-development-with-ai/">Spec-Driven Development: How We Use AI Without Gambling Your Budget</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/spec-driven-development-with-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPress Alternatives for AI-Driven Websites: What to Consider Without Falling Into a Trap</title>
		<link>https://www.macronimous.com/blog/wordpress-alternatives-for-ai-driven-websites/</link>
					<comments>https://www.macronimous.com/blog/wordpress-alternatives-for-ai-driven-websites/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Wed, 21 Oct 2026 06:48:06 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[CMS]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[AI Development]]></category>
		<category><![CDATA[Headless WordPress]]></category>
		<category><![CDATA[Kirby]]></category>
		<category><![CDATA[ProcessWire]]></category>
		<category><![CDATA[Statamic]]></category>
		<category><![CDATA[Wordpress]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5427</guid>

					<description><![CDATA[<p>A good WordPress alternative for AI-driven websites is one your client can edit without help, a second developer can take over without guesswork, and an AI coding assistant can build and maintain from plain code. ProcessWire, Kirby and Statamic all qualify for small business sites. Judge them on those three things, not on how familiar [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-alternatives-for-ai-driven-websites/">WordPress Alternatives for AI-Driven Websites: What to Consider Without Falling Into a Trap</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/10/WordPress-Alternatives-for-AI-Driven-Websites.jpg"><img decoding="async" width="2492" height="1316" src="https://www.macronimous.com/blog/wp-content/uploads/2026/10/WordPress-Alternatives-for-AI-Driven-Websites.jpg" alt="WordPress Alternatives for AI-Driven Websites" class="aligncenter size-full wp-image-5430" /></a>
<div class="mac-direct-answer">
<p><strong>A good <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">WordPress</a> alternative for AI-driven websites</strong> is one your client can edit without help, a second developer can take over without guesswork, and an <a href="https://www.macronimous.com/blog/is-vibe-coding-bad/">AI coding</a> assistant can build and maintain from plain code. ProcessWire, Kirby and Statamic all qualify for small business sites. Judge them on those three things, not on how familiar the admin screen looks.</p>
</div>
<p>Twenty years ago we built our own CMS at Macronimous. It started in classic ASP, moved to ASP.NET, and we retired it when we moved our clients to WordPress and Drupal. It wasn&#8217;t a bad system. It just couldn&#8217;t keep up with a plugin ecosystem that was growing every week, and our clients didn&#8217;t want a CMS only we understood.</p>
<p>So it&#8217;s a little strange to be looking past WordPress again. But <a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI</a> has changed the math. When a coding assistant can turn an approved design into templates in an afternoon, the things that made WordPress the safe default (themes, page builders, a plugin for everything) matter less. What matters more is everything that happens after launch.</p>
<p>This isn&#8217;t a ranking. We haven&#8217;t tested every CMS and we don&#8217;t intend to. It&#8217;s the list of things we now check before recommending anything that isn&#8217;t WordPress, plus what one real rebuild taught us.</p>
<h2>The first trap: judging a CMS by its admin screen</h2>
<p>We rebuilt <a href="https://www.promotefirst.com/" target="_blank" rel="noopener noreferrer">promotefirst.com</a>, our own small marketing site, on <a href="https://processwire.com/" target="_blank" rel="noopener noreferrer">ProcessWire</a>. Twenty pages, the same URLs, a contact form, an XML sitemap, and <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">SEO</a> fields on every page.</p>
<p>I logged in to the admin myself. My first reaction was that it looked a bit immature, and it reminded me of our old ASP.NET CMS. My second reaction, after an hour, was that I could live with it. It&#8217;s a plain page tree and plain forms. Nothing flashy, and nothing a client can break.</p>
<p>That first reaction is the trap. Anyone who has spent ten years in the WordPress dashboard will find every other admin &#8220;wrong&#8221; for a day or two. If you (or your client) can come at it without that prejudice, ProcessWire is a genuinely good alternative. If you can&#8217;t, no amount of technical merit will save the project, and it&#8217;s better to know that before you start.</p>
<h2>What one real rebuild actually showed us</h2>
<p>The design moved from static HTML to ProcessWire templates with no visual change at all. We checked that by screenshotting every page from both versions at phone and desktop widths and comparing them pixel by pixel. That&#8217;s possible because a ProcessWire template is just a PHP file that prints your HTML. There&#8217;s no theme layer adding its own markup.</p>
<p>The surprises were all in the details a client would hit:</p>
<ul>
<li><strong>The admin needed writing.</strong> Out of the box, field labels are developer labels. Every friendly name and hint (&#8220;Aim for 50 to 60 characters&#8221;) was written by hand. Budget for it.</li>
<li><strong>Images can fail in a confusing way.</strong> Images saved from many websites are WebP files with a .jpg name. ProcessWire trusts the file name, so the admin preview broke and showed &#8220;not a supported image type&#8221;. A client would assume the site was broken.</li>
<li><strong>Some basics cost extra.</strong> The official form builder and the full-page cache are paid Pro modules. That&#8217;s fair pricing, but it isn&#8217;t free like the WordPress equivalents.</li>
<li><strong>Staging behind a CDN needs care.</strong> Our password-protected test site sat behind Cloudflare, which cached a couple of files from it and served them publicly until we sent a &#8220;private&#8221; cache header. That&#8217;s a hosting lesson more than a CMS one, but every alternative you test will hit it.</li>
</ul>
<p>Our rough estimate for a developer new to ProcessWire: about 35 hours for a 20-page site the first time, against 25 to 30 for the same job in WordPress. The second project drops to around 20, because the setup script and templates carry over. Treat those as estimates from one project, not benchmarks.</p>
<h2>The second trap: pricing the licence and forgetting the people</h2>
<p>A free CMS can be the expensive choice. The real costs sit in three places: paid add-ons, the hours needed to make the admin friendly, and how many developers can maintain it after you.</p>
<table class="styled-table">
<thead>
<tr>
<th>Option</th>
<th>Built on</th>
<th>What you&#8217;ll pay for</th>
<th>Who can maintain it</th>
</tr>
</thead>
<tbody>
<tr>
<td>ProcessWire</td>
<td>PHP, MySQL</td>
<td>Free core; paid Pro modules for forms and caching</td>
<td>Any PHP developer, after a day of learning</td>
</tr>
<tr>
<td>Kirby</td>
<td>PHP, no database (content is files)</td>
<td>A paid per-site licence</td>
<td>Any PHP developer; content can live in Git</td>
</tr>
<tr>
<td>Statamic</td>
<td>Laravel (PHP 8.3+)</td>
<td>Free Core with one admin user; Pro is $349 per site</td>
<td>Developers with Laravel experience</td>
</tr>
<tr>
<td><a href="https://www.macronimous.com/blog/is-headless-wordpress-worth-it/">Headless WordPress</a></td>
<td>WordPress plus a JavaScript front end</td>
<td>Free software, but two codebases to host and maintain</td>
<td>Two skill sets, documented together</td>
</tr>
</tbody>
</table>
<p>Statamic deserves a note. It needs Laravel experience, which a WordPress-only team won&#8217;t have. That&#8217;s manageable, and Laravel developers are easy to hire, but check your hosting first. <a href="https://statamic.dev/requirements" target="_blank" rel="noopener noreferrer">Statamic requires PHP 8.3 or newer</a>, and the shared server we used for the ProcessWire trial was still on 8.2.</p>
<p>The people question is the one agencies skip. If the one developer who knows your chosen CMS leaves, your client is stuck. WordPress&#8217;s biggest advantage was never technical. It&#8217;s that you can hand a WordPress site to almost anyone.</p>
<h2>The third trap: forgetting who writes the code now</h2>
<p>Here&#8217;s the criterion that didn&#8217;t exist five years ago: how well can an AI coding assistant build and maintain this CMS?</p>
<p>It matters because more of the routine work (templates, field setup, content imports, fixes) now goes through an assistant, with a developer reviewing. A CMS where the important configuration lives in code is easy to work with that way. A CMS that&#8217;s mostly configured by clicking through admin screens isn&#8217;t, because an assistant can&#8217;t see those clicks, and neither can the next developer.</p>
<p>ProcessWire did well here. Every field, template, user role and imported page in our rebuild came from one script we could read, review and re-run. Kirby goes further, defining each page type in a short text file. We&#8217;ve written before about keeping <a href="https://www.macronimous.com/blog/controlled-ai-coding/">AI coding under control</a>, and the same rule applies: if you can&#8217;t review it as code, you can&#8217;t trust what the assistant did to it.</p>
<div class="mac-key-point">
<p>Pick the CMS whose setup you can read as code. That&#8217;s the one an AI assistant can maintain, and the one the next developer can understand.</p>
</div>
<h2>AI-generated HTML is a different problem</h2>
<p>There&#8217;s a second use case that keeps coming up. A client arrives with a site generated by an AI builder: clean enough HTML, no CMS, and they want to edit the text. Rebuilding it as CMS templates defeats the point of generating it.</p>
<p>For that, you want a tool that marks parts of existing HTML as editable instead of converting the whole site. Three worth a look, which we haven&#8217;t tested yet:</p>
<ul>
<li><strong>CouchCMS</strong> is self-hosted PHP. You wrap regions of your HTML in its tags and an admin appears (<a href="https://docs.couchcms.com/concepts/editable-regions.html" target="_blank" rel="noopener noreferrer">editable regions are explained in its docs</a>). It&#8217;s free, but the project is small.</li>
<li><strong>CloudCannon</strong> is hosted and Git-based. You add attributes to the HTML and clients edit directly on the page. It&#8217;s polished, and it&#8217;s a monthly subscription.</li>
<li><strong>Surreal CMS</strong> is hosted too. It connects to your existing server and lets clients click and type. There&#8217;s nothing to install.</li>
</ul>
<p>The question to ask all three: what happens when you regenerate the HTML next month? If the client&#8217;s edits vanish, the tool isn&#8217;t the answer. We compared AI builders and WordPress for search in an <a href="https://www.macronimous.com/blog/ai-website-builder-vs-wordpress-for-seo/">earlier post on AI website builders</a>, and editing after launch was the weak spot then too.</p>
<h2>Don&#8217;t close the door on WordPress</h2>
<p>Some people will tell you headless WordPress is pointless because newer headless systems are better. For a blank-page project, they may be right. But most sites don&#8217;t start from a blank page. They start with hundreds of posts, years of SEO metadata and an editorial team that already knows the dashboard.</p>
<p><a href="https://www.macronimous.com/services/cms-development/wordpress-to-headless/">Moving WordPress to a headless setup</a> keeps all of that and gives you a faster front end. It&#8217;s not the best headless CMS. It&#8217;s often the cheapest road to headless for a site that&#8217;s already on WordPress.</p>
<p>If you go that way, write the handoff document nobody writes: which WordPress fields and plugins the front end depends on, how a Publish reaches the live site, where the keys live, and how previews work. Without it, the next developer spends two weeks working out how the two halves talk. (Old WordPress sites carry their own <a href="https://www.macronimous.com/blog/hidden-technical-debt-wordpress-seo/">hidden technical debt</a>, so check that before you decide anything.)</p>
<h2>Before you pick a WordPress alternative for an AI-driven website</h2>
<ul class="mac-checklist">
<li>Have the client (not just the developer) use the admin for an hour before deciding.</li>
<li>List every paid add-on you&#8217;ll need: forms, caching, redirects, backups.</li>
<li>Check the hosting: PHP version, database, and whether a CDN sits in front.</li>
<li>Ask who else could maintain this site if your developer left.</li>
<li>Make sure fields, templates and roles are defined in code you can review.</li>
<li>Upload a real image from the client&#8217;s phone and from a website, and see what breaks.</li>
<li>Write down how a published change reaches the live site.</li>
</ul>
<p>For a 20-page business site that a client edits a few times a month, ProcessWire passed most of that list for us. For shops, plugin-heavy blogs, or clients who want to build their own layouts, we&#8217;d still choose WordPress. That&#8217;s the honest answer, and it&#8217;s the one we give clients.</p>
<div class="mac-cta-box">
<h3>Choosing between WordPress and an alternative?</h3>
<p>We&#8217;ll look at what your site needs after launch, who will edit it and who will maintain it, and recommend the platform that fits, even if that&#8217;s WordPress.</p>
<p><a href="https://www.macronimous.com/services/custom-web-development/" class="mac-cta-button">Get a platform recommendation</a>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/wordpress-alternatives-for-ai-driven-websites/">WordPress Alternatives for AI-Driven Websites: What to Consider Without Falling Into a Trap</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-alternatives-for-ai-driven-websites/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Is Vibe Coding Bad? What a Real Invoice App Build Showed us</title>
		<link>https://www.macronimous.com/blog/is-vibe-coding-bad/</link>
					<comments>https://www.macronimous.com/blog/is-vibe-coding-bad/#respond</comments>
		
		<dc:creator><![CDATA[Jeffy Susan]]></dc:creator>
		<pubDate>Thu, 15 Oct 2026 06:31:46 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[agentic coding]]></category>
		<category><![CDATA[AI code review]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Vibe coding]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5416</guid>

					<description><![CDATA[<p>Is vibe coding bad? No. It&#8217;s a fast, cheap way to get a prototype or an internal tool working, and I use it most days. It goes wrong when the software has to follow rules the AI was never told about: tax law, money, permissions, two users at the same second. On our own invoice [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/is-vibe-coding-bad/">Is Vibe Coding Bad? What a Real Invoice App Build Showed us</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/10/Is-Vibe-Coding-Bad.jpg"><img loading="lazy" decoding="async" width="1734" height="907" src="https://www.macronimous.com/blog/wp-content/uploads/2026/10/Is-Vibe-Coding-Bad.jpg" alt="Is Vibe Coding Bad" class="aligncenter size-full wp-image-5421" /></a>
<div class="mac-direct-answer">
<p><strong>Is <a href="https://www.macronimous.com/blog/testing-in-agentic-coding/">vibe coding</a> bad?</strong> No. It&#8217;s a fast, cheap way to get a prototype or an internal tool working, and I use it most days. It goes wrong when the software has to follow rules the <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">AI</a> was never told about: tax law, money, permissions, two users at the same second. On our own invoice app, four such problems came up. Each one looked like correct code, and each needed someone who knew the business to deal with it.</p>
</div>
<div class="mac-toc">
<p class="mac-toc-title">In this post</p>
<ul>
<li><a href="#what-it-is-good-at">Is vibe coding bad? It&#8217;s good at more than developers admit</a></li>
<li><a href="#the-app">The app: our own invoicing system</a></li>
<li><a href="#four-cases">Four things the AI got wrong, or would have</a></li>
<li><a href="#security">And vibe coding security risks?</a></li>
<li><a href="#comparison">A vibe coder alone vs. a seasoned developer using AI</a></li>
<li><a href="#replace-developers">Will AI replace web developers?</a></li>
<li><a href="#questions-to-ask">Questions to ask before you hand over a real system</a></li>
<li><a href="#faq">FAQ</a></li>
</ul>
</div>
<h2 id="what-it-is-good-at">Is vibe coding bad? It&#8217;s good at more than developers admit</h2>
<p>The pitch I keep hearing goes like this: AI writes the code now, so you don&#8217;t need web developers with ten or fifteen years behind them. You need one person who&#8217;s good at prompting.</p>
<p>I&#8217;m not going to tell you that&#8217;s nonsense, because half of it is true.</p>
<p>Vibe coding (Andrej Karpathy&#8217;s name for it, from early 2025) means describing what you want and accepting what the AI writes without reading much of it. For a prototype, or an internal tool that five people use, that&#8217;s fine. Better than fine. I&#8217;ve run a <a href="https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/">web development</a> company since 2002 and I build with Claude Code myself, and jobs that used to take my team two weeks now take an afternoon. Anyone who tells you the old way is still faster is protecting a rate card.</p>
<p>So the gap between a beginner with AI and a senior developer <em>is</em> shrinking, if you measure it by how much working code each can turn out in a day. Where it hasn&#8217;t shrunk is harder to see, and the only honest way I know to show it is with a real build. This will get a little technical (I&#8217;ll keep the code to one line, promise).</p>
<h2 id="the-app">The app: our own invoicing system</h2>
<p>Macronimous exports software services from India, which means our invoices follow rules that ordinary invoicing tools don&#8217;t model. We couldn&#8217;t find an off-the-shelf product that tied a bank&#8217;s remittance certificate to the invoice it paid, so we built our own in PHP and MySQL, with an AI agent doing most of the typing. It was quick. The code was clean.</p>
<p>I&#8217;ve already written up the full story for developers, with commits and diffs, in <a href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">our post on agentic coding mistakes</a>. What follows is the shorter version, for people who pay for software instead of writing it.</p>
<p>Four things happened. Not one of them threw an error.</p>
<h2 id="four-cases">Four things the AI got wrong, or would have</h2>
<h3>1. The Void button</h3>
<p>Ask any AI for invoicing software and it&#8217;ll handle a wrong invoice the way every accounting tutorial does: mark it void, keep the number, never delete anything. Ours built exactly that, with a button and a confirmation box, and it worked perfectly in the demo.</p>
<p>It was also wrong for us. In our reading of Indian GST (Goods and Services Tax), an issued tax invoice creates a liability whether you later mark it void or not. (That&#8217;s our working understanding, not tax advice; ask your accountant.) A wrongly issued invoice has to be removed and its proforma put back. I caught it in review, and only because I&#8217;ve lived with these rules for years. A perfectly good vibe coder who has never run an export business would have shipped it, and as far as I can tell the first sign of trouble would&#8217;ve been a notice from the tax department.</p>
<h3>2. The April 1 problem</h3>
<p>Here&#8217;s the one line of code I promised. It&#8217;s from the first version of the function that numbers our invoices:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">return sprintf('%s-%s-%03d', $row['prefix'], date('Y', strtotime($issueDate)), $seq);</pre><p>&nbsp;</p>
<p>There&#8217;s nothing wrong with it. <code>date('Y')</code> is the calendar year, which is what most of the world wants. But India&#8217;s financial year starts on April 1, and our invoice numbers restart with it. The fix began with one sentence from me in review: &#8220;every business year, April 1st, we change the invoice numbers.&#8221;</p>
<p>You can&#8217;t find that bug by reading the code, because the code is correct. You find it by knowing something the code doesn&#8217;t.</p>
<h3>3. Two people, one invoice number</h3>
<p>This one I didn&#8217;t catch by knowing anything. I asked a question: what if someone on my team creates an invoice on their machine at the same moment I do?</p>
<p>Nobody knew. So we tested it with twelve at once, and eleven failed. The numbering code read the counter and then updated it in a second step, and under simultaneous writes MySQL <a href="https://dev.mysql.com/doc/refman/8.4/en/innodb-deadlocks.html" target="_blank" rel="noopener noreferrer">deadlocks on that pattern</a> (two requests each wait for the other, and one gets thrown out). The fix was a single atomic statement, meaning one database command that reads and increments together so nothing can squeeze in between.</p>
<p>I&#8217;d love to say experience handed me the answer. It didn&#8217;t. It handed me the question, because I&#8217;ve seen &#8220;it works on my machine&#8221; turn into a support call before.</p>
<h3>4. The bug that was never written</h3>
<p>AI tools like to store a payment as a yes-or-no flag on the invoice. Ours never did, and I was half expecting it to. Before it wrote a line, our spec said every foreign payment must carry its FIRC (Foreign Inward Remittance Certificate, the bank&#8217;s proof that the money arrived from abroad). A yes-or-no flag can&#8217;t hold a certificate, so the AI built a proper payments table from the very first commit.</p>
<div class="mac-key-point">
<p>The only mistake that cost us nothing was the one the spec prevented, and the spec was written by someone who knew the business.</p>
</div>
<h2 id="security">And vibe coding security risks?</h2>
<p>Fair question, since security is what most people asking about vibe coding are really worried about. The four cases above aren&#8217;t security bugs. Two others from our internal apps are, and I&#8217;ll be straight with you: my notes don&#8217;t say who spotted them, so I&#8217;m not going to claim them for experience.</p>
<ul>
<li>A read-only &#8220;viewer&#8221; role showed up in the interface and was never enforced on the server. Viewers could create and delete records.</li>
<li>A spreadsheet export would have <a href="https://owasp.org/www-community/attacks/CSV_Injection" target="_blank" rel="noopener noreferrer">run a formula on opening</a> if a client&#8217;s name began with an equals sign.</li>
</ul>
<p>Both passed a click-through. And both are the sort of thing an AI won&#8217;t raise unless you ask, which you only do if you&#8217;ve been bitten.</p>
<h2 id="comparison">A vibe coder alone vs. a seasoned developer using AI</h2>
<p>Look at the heading on the third column before you read the table. It doesn&#8217;t say &#8220;developer without AI,&#8221; because that person is losing ground too. People frame this as vibe coding vs traditional coding, and I don&#8217;t think that&#8217;s the real choice anymore. Everyone&#8217;s using the AI. What differs is whether anybody who understands the business reads what comes out.</p>
<table class="styled-table">
<thead>
<tr>
<th>Area</th>
<th>Vibe coder alone</th>
<th>Seasoned developer using AI</th>
<th>What it costs you</th>
</tr>
</thead>
<tbody>
<tr>
<td>Requirements and business rules</td>
<td>Builds the textbook version of the prompt.</td>
<td>Knows where your industry or country differs.</td>
<td>Our Void button would have created a tax liability.</td>
</tr>
<tr>
<td>Architecture</td>
<td>Accepts the AI&#8217;s first design.</td>
<td>Weighs hosting cost and upkeep.</td>
<td>You outgrow the design and pay twice.</td>
</tr>
<tr>
<td>Database</td>
<td>Accepts the generated tables.</td>
<td>Checks indexes and simultaneous writes.</td>
<td>Eleven of twelve invoices failed in our test.</td>
</tr>
<tr>
<td>Performance</td>
<td>Fixes it when users complain.</td>
<td>Spots the expensive query before launch.</td>
<td>The slowdown arrives with your busiest week.</td>
</tr>
<tr>
<td>Security</td>
<td>Trusts the generated login and roles.</td>
<td>Logs in as each role and tries to misbehave.</td>
<td>Our &#8220;viewer&#8221; could delete records.</td>
</tr>
<tr>
<td>Testing</td>
<td>Runs the tests the AI offered.</td>
<td>Decides what&#8217;s risky and tests that.</td>
<td>Customers find the bugs for you.</td>
</tr>
<tr>
<td>Faults on the live site</td>
<td>Pastes the error back into the AI.</td>
<td>Reads the logs and finds the cause.</td>
<td>The outage runs longer than it should.</td>
</tr>
<tr>
<td>Existing code</td>
<td>Wants to rebuild it.</td>
<td>Can change a site somebody else built.</td>
<td>You fund a rebuild you didn&#8217;t need.</td>
</tr>
<tr>
<td>Search visibility</td>
<td>Ships pages that only render in the browser.</td>
<td>Checks what Google and AI crawlers receive.</td>
<td>The site works and nobody finds it.</td>
</tr>
<tr>
<td>Ownership</td>
<td>Says the AI wrote it.</td>
<td>Signs off on it.</td>
<td>There&#8217;s nobody to call when it breaks.</td>
</tr>
</tbody>
</table>
<h2 id="replace-developers">Will AI replace web developers?</h2>
<p>Some of them, yes. If all a developer offers is typing speed and framework syntax, the AI has that job already.</p>
<p>What I can&#8217;t see it replacing soon (and I&#8217;ve been wrong about timelines before) is the person who knows what the software is <em>for</em>. The AI gives you the most common version of whatever you ask for, which is the right version if yours is the most common business, and almost nobody&#8217;s is, because you&#8217;ve got a tax rule or a shipping exception or a client who pays in three currencies or an auditor who wants the record kept a certain way, and none of that is in the prompt unless somebody who has lived with it puts it there, and that somebody is what you&#8217;re paying for when you hire an expert.</p>
<p>Go back over the four cases. Three were caught by knowledge of the business, not of PHP. The fourth was caught by a question that came from years of watching software meet real users. I know that&#8217;s a small sample (one app, one company), so treat it as a pattern I&#8217;ve seen and not a study.</p>
<h2 id="questions-to-ask">Questions to ask before you hand over a real system</h2>
<ul class="mac-checklist">
<li>Does this software touch money, tax, personal data or legal records?</li>
<li>Who on the project knows the rules of my industry and my country, and have they read the spec?</li>
<li>What happens when two people do the same thing at the same second, and is there a test that proves it?</li>
<li>Has anyone logged in as each user role and tried to do what that role shouldn&#8217;t?</li>
<li>When it breaks at night, who reads the logs?</li>
<li>Could a different developer pick up this code a year from now?</li>
</ul>
<p>If the honest answer to the first one is no, go ahead and vibe code it. I would. We&#8217;ve written about <a href="https://www.macronimous.com/blog/vibe-coding-for-web-developers-amplify-your-flow-state-with-ai/">how our developers use vibe coding</a>, and about the stricter process we call <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled AI coding</a> for everything else.</p>
<h2 id="faq">FAQ</h2>
<div>
<div class="faq-item">
<h3>Is vibe coding bad for production apps?</h3>
<p>It&#8217;s risky when nobody who understands the business reviews the result. Vibe coding produces the most conventional version of what you asked for. If your app handles money, tax, permissions or several users at once, the conventional version can be wrong in ways that never show up as an error.</p>
</div>
<div class="faq-item">
<h3>What are the main vibe coding security risks?</h3>
<p>The ones we&#8217;ve met in our own apps were a user role that existed on screen but wasn&#8217;t enforced on the server, and a spreadsheet export open to formula injection. Both worked in a demo. Neither gets flagged unless someone tests for it on purpose.</p>
</div>
<div class="faq-item">
<h3>Will AI replace web developers?</h3>
<p>It&#8217;s replacing the typing. It isn&#8217;t replacing the person who knows your tax rules, your data and what happens under load. On our invoice app, three of four problems were caught by business knowledge and one by a question about simultaneous users.</p>
</div>
<div class="faq-item">
<h3>Can a non-developer build a business app with AI?</h3>
<p>Yes, and for a prototype or a small internal tool it&#8217;s often the right call. Before it handles real customers or real money, have a developer who knows your domain review the business rules, the database design and the user roles.</p>
</div>
</div>
<h2>Where that leaves you</h2>
<p>That&#8217;s what one build taught us. If you&#8217;ve put a fully vibe-coded system into production and it has held up for a year, I&#8217;d like to hear how, because I haven&#8217;t seen it yet.</p>
<div class="mac-cta-box">
<h3>Have an AI-built app that&#8217;s about to go live?</h3>
<p>We&#8217;ll read it the way we read our own: business rules first, then the data, the user roles and what happens when two people click at once.</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/is-vibe-coding-bad/">Is Vibe Coding Bad? What a Real Invoice App Build Showed us</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-vibe-coding-bad/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 loading="lazy" 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>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 <a href="https://www.macronimous.com/blog/is-vibe-coding-bad/">vibe coding</a> 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>Fluency Without Keystrokes: Web Developers in the AI Era</title>
		<link>https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/</link>
					<comments>https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/#respond</comments>
		
		<dc:creator><![CDATA[Benny]]></dc:creator>
		<pubDate>Fri, 10 Jul 2026 05:10:20 +0000</pubDate>
				<category><![CDATA[AI Web Development]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[AI Development]]></category>
		<category><![CDATA[Web Development]]></category>
		<category><![CDATA[AI Coding]]></category>
		<category><![CDATA[AI in web development]]></category>
		<category><![CDATA[AI web development]]></category>
		<guid isPermaLink="false">https://www.macronimous.com/blog/?p=5259</guid>

					<description><![CDATA[<p>Will AI replace web developers? It will replace the part of the job that was always mechanical: translating a decided design into working syntax. It will not replace knowing what is being built, what it is built on, and why. I call that second skill fluency without keystrokes, and it is the one I have [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/">Fluency Without Keystrokes: Web Developers in the AI Era</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/Fluency-Without-Keystrokes.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-5263 size-large" src="https://www.macronimous.com/blog/wp-content/uploads/2026/07/Fluency-Without-Keystrokes-1024x576.png" alt="Will AI replace Web Developers" width="1024" height="576" /></a>
<div class="mac-direct-answer">
<p><strong>Will <a href="https://www.macronimous.com/blog/wordpress-7-0-ai-the-token-cost-reality-for-site-owners/">AI</a> replace web developers?</strong> It will replace the part of the job that was always mechanical: translating a decided design into working syntax. It will not replace knowing what is being built, what it is built on, and why. I call that second skill fluency without keystrokes, and it is the one I have run a development agency on since I stopped writing production code in 2005.</p>
</div>
<h2>I wrote my last production code in 2005</h2>
<p>That is not a confession. It is the whole argument.</p>
<p>I learned to program in 1991 and coded for fourteen years. Then I founded <a href="http://www.Macronimous.com" target="_blank" rel="noopener">Macronimous</a>. Within just a couple of years, my developers were writing the code and I was doing something else. In the twenty-four years since, I have not shipped a single production function. And I have never been more useful to a development project than I am right now, in the middle of the AI era everyone says will end careers like the one I supposedly abandoned.</p>
<p>Here is what I did keep doing: I watched every shift from the inside. Desktop applications to web applications. Web apps to multi-tier architectures. Multi-tier to hosted solutions. Hosted to SaaS. Now SaaS to whatever AI-assisted development turns out to be. Each time, the syntax changed completely. Each time, the underlying questions did not change at all: where does the data live, who is allowed to touch it, what happens under load, what does the client actually need versus what they asked for.</p>
<p>If your value was typing, every one of those shifts hurt you. If your value was the questions, every one of those shifts made you more necessary, because suddenly a whole team needed someone who could tell which parts of their hard-won knowledge still applied.</p>
<h2>The knowledge that survived every shift</h2>
<p>People assume that when you stop coding, the knowledge decays uniformly. It does not. The syntax goes fast. I could not write a working React component from memory today, and I do not pretend otherwise to my team. But four things never decayed, because they are not syntax:</p>
<ul>
<li><strong>Databases.</strong> Normalization, indexing, why a query is slow, why &#8220;we&#8217;ll fix the schema later&#8221; is the most expensive sentence in software. Every stack I have watched arrive, from LAMP to serverless, sat on top of the same relational logic.</li>
<li><strong>Algorithms.</strong> Not competitive-programming puzzles. The working instinct for what is O(n) and what is O(n²), and why the feature that demos fine with 50 records dies with 50,000.</li>
<li><strong>A few languages, deeply.</strong> Not many, shallowly. Once you have genuinely understood two or three, you can read almost anything. Reading is the skill that matters now; more on that below.</li>
<li><strong>Servers and deployment.</strong> What actually happens between a browser request and a response. DNS, HTTP, processes, memory. Abstractions changed names five times; the physics never did.</li>
</ul>
<p>That list, plus two decades of translating client language into developer language and back, is the entire toolkit. Notice what is not on it: any framework, any editor, any AI tool. Those are all replaceable, and all of them have been replaced several times during my career.</p>
<h2>The &#8220;that&#8217;s not possible&#8221; test</h2>
<p>The clearest place this toolkit earns its keep is a moment every project manager knows: a developer says <em>that&#8217;s not possible</em>.</p>
<p>Sometimes they are right. But over the years, especially during native mobile app projects where platform limitations are real and frequent, I developed a simple piece of reasoning for that moment. If the feature is a reasonable thing for a business to want, then someone else has almost certainly wanted it before us. Which means either the tracks are already laid, in a third-party API, a plugin, an extension, a Stack Exchange thread, or a discussion buried in some project&#8217;s issue tracker, or the tracks are absent and we get to lay them ourselves.</p>
<p>So the answer to &#8220;that&#8217;s not possible&#8221; is rarely yes or no. It is: <em>not possible the way we are currently holding the problem.</em> Go look for the tracks. More than once we found them somewhere the team had not thought to look. More than once we did not, built the workaround ourselves, absorbed cost the project budget never covered, delivered the feature anyway, and then reused that solution on later projects until it paid for itself.</p>
<p><!-- BENNY: if you want to name one concrete project/feature here, this is the spot. One or two sentences max. The paragraph above stands on its own if not. --></p>
<p>Here is the part that matters for the AI question. That reasoning move is not a coding skill. I could not have implemented a single one of those workarounds myself. But I could not have made the move without knowing what databases, servers, and APIs can and cannot do, because &#8220;reasonable thing to want&#8221; is a judgment about the underlying system, not about the client&#8217;s mood. Strip out the fundamentals and the same sentence becomes an empty pep talk.</p>
<p>Now notice what AI does to this moment: it removes it. An <a href="https://www.macronimous.com/blog/agentic-coding-domain-knowledge/">agentic coding</a> platform never says <em>that&#8217;s not possible</em>. Ask, and it builds. Ask for something unwise, and it builds that too, politely and fast. The schema that will not survive real data, the authentication shortcut, the feature that quietly breaks three others. Something ships for every request.</p>
<p>That is a more dangerous failure mode than a developer&#8217;s premature no. A false &#8220;not possible&#8221; costs you a feature. A false &#8220;possible&#8221; costs you a production incident, months later, after the demo already convinced everyone. For twenty years my job was seeing through the first. The job now is seeing through the second, and it takes exactly the same knowledge of what is underneath. The tool stopped saying no, so someone in the room has to know when the answer should have been no.</p>
<h2>What AI actually replaces</h2>
<p>Fred Brooks sorted this out in 1986, before most of today&#8217;s developers were born. In <a href="https://www.cs.unc.edu/techreports/86-020.pdf" target="_blank" rel="noopener noreferrer">No Silver Bullet</a>, he split software difficulty into essence, the specification and design of the conceptual construct, and accident, the labor of representing that construct in code. His claim was that no tool would ever give an order-of-magnitude win, because tools only attack the accident.</p>
<p><a href="https://www.macronimous.com/blog/the-code-your-ai-wants-to-delete-is-load-bearing/">AI coding</a> tools are the strongest attack on the accident ever built. We use Claude Code, Copilot, and Cursor across our React and PHP work daily, and the keystroke savings are real. But look at what developers themselves report. In the <a href="https://survey.stackoverflow.co/2025/ai" target="_blank" rel="noopener noreferrer">Stack Overflow 2025 survey</a>, 84% of developers use or plan to use AI tools, yet the most-cited frustration, at 66%, is output that is almost right but not quite. And when asked why they would still turn to a human in an AI-heavy future, the top answer was simple: when they don&#8217;t trust the AI&#8217;s answer.</p>
<p>Almost right but not quite is precisely the failure mode that only essence-level knowledge can catch. The code compiles. The demo works. The schema decision buried inside it will cost you a migration in eight months. Somebody on the team has to be the person who sees that, and that person is doing my job, not the typist&#8217;s.</p>
<table class="styled-table">
<thead>
<tr>
<th>AI replaces</th>
<th>AI does not replace</th>
</tr>
</thead>
<tbody>
<tr>
<td>Translating a decided design into syntax</td>
<td>Deciding what should be built, and why</td>
</tr>
<tr>
<td>Boilerplate, scaffolding, test stubs</td>
<td>Knowing when the answer should have been no</td>
</tr>
<tr>
<td>Looking up API signatures and idioms</td>
<td>Judging whether an almost-right answer is safe to ship</td>
</tr>
<tr>
<td>First drafts of almost everything</td>
<td>Translating client language into system language and back</td>
</tr>
</tbody>
</table>
<h2>Fluency without keystrokes</h2>
<p>So here is the concept I have been circling, and the name I would put on it: <strong>fluency without keystrokes</strong>. It is the ability to read code, architecture, and constraints well enough to reason about them, without being the person who produces them. Knowing what is being built, what it is built on, and why.</p>
<div class="mac-key-point">
<p>AI replaces keystrokes. It has no opinion about what is worth building, what it sits on, or why. The person who holds those answers was never the typist, and cannot be replaced by a better typist.</p>
</div>
<p>I want to be precise about what this is not. It is not &#8220;ideas guy&#8221; hand-waving; the fluency has to be real, tested against actual systems, or the &#8220;that&#8217;s not possible&#8221; test collapses into wishful thinking. And it is not an excuse to skip the fundamentals; the whole point is that I earned the fluency the slow way, through eleven years of coding and twenty-four years of watching code fail in production. What the AI era changes is the ratio. You no longer need decades of keystrokes to keep the fluency alive. You need the foundations, plus the habit of reading and reasoning about what the machines produce. That is why I keep arguing for a <a href="https://www.macronimous.com/blog/controlled-ai-coding/">controlled approach to AI coding</a> rather than the pure <a href="https://www.macronimous.com/blog/vibe-coding-for-web-developers-amplify-your-flow-state-with-ai/">vibe coding flow</a>: control requires exactly this kind of fluency, and exercising it is how you keep it.</p>
<h2>So how much code should you learn?</h2>
<p>Enough to read it like a native. That is the honest answer, and it is less code than the bootcamps sell and more than the &#8220;just learn prompting&#8221; crowd admits.</p>
<p>The prevailing advice right now splits into two equally bad camps. One says syntax is dead, learn to prompt. The other says nothing has changed, grind the same curriculum harder. Both camps make the same mistake: they treat coding-as-typing as the job. The job was always understanding systems; typing was just how we proved it. AI removed the proof, not the requirement.</p>
<p>If I were starting out in web development today, this is what I would actually put the hours into:</p>
<ul class="mac-checklist">
<li>One backend language learned deeply enough to debug someone else&#8217;s code in it, not three learned to tutorial depth</li>
<li>SQL and data modeling, properly. This is the least glamorous item and the one that will pay you the longest</li>
<li>How the web actually works: HTTP, DNS, what a server does, where things run and why. Trace one full request end to end until nothing in it is magic</li>
<li>Reading AI-generated code critically, every day. Ask why it chose what it chose. This is the modern equivalent of code review under a senior, and it is how <a href="https://www.macronimous.com/blog/writing-clean-code-with-ai/">clean code with AI</a> actually happens</li>
<li>The client&#8217;s language. Requirements, constraints, budget, the gap between what they asked for and what they need. No model translates this yet, and every stage of the <a href="https://www.macronimous.com/blog/web-development-life-cycle-process-flow-diagram/">web development life cycle</a> depends on someone who can</li>
</ul>
<p>Five to ten years from now I do not know whether developers will still be typing much code. I am certain there will still be code, and certain that every team will still need at least one person who is fluent in what it means. In 2002 I bet my career that the fluency mattered more than the keystrokes. Twenty-four years later, AI just proved the bet.</p>
<h2>Frequently asked questions</h2>
<div id="fluency-keystrokes-faq">
<div class="faq-item">
<h3>Is web development still worth learning in 2026?</h3>
<p>Yes, if you learn it as systems knowledge rather than syntax production. Demand for people who can type framework code from memory is shrinking. Demand for people who can read code, model data, and judge whether AI output is safe to ship is not. Learn the second skill set; the first now comes bundled with it.</p>
</div>
<div class="faq-item">
<h3>How much coding should I learn if AI writes the code?</h3>
<p>Enough to debug someone else&#8217;s code in one backend language, write real SQL, and trace an HTTP request end to end. That is less breadth than the traditional curriculum and more depth than prompt-engineering courses offer. Reading fluency is the target; typing speed is now optional.</p>
</div>
<div class="faq-item">
<h3>Which web development skills can AI not replace?</h3>
<p>Deciding what should be built and why, translating client requirements into system constraints, judging &#8220;almost right&#8221; output before it reaches production, and recognizing when the answer should have been no. Agentic tools build whatever you ask, wise or not. All four skills require fundamentals; none of them require keystrokes.</p>
</div>
</div>
<div class="mac-cta-box">
<h3>Need a team that reasons past &#8220;that&#8217;s not possible&#8221;?</h3>
<p>Macronimous has been building web applications since 2002, through every platform shift including this one. We bring the fundamentals; the AI brings the keystrokes.</p>
<p><a class="mac-cta-button" href="https://www.macronimous.com/services/custom-web-development/">Discuss your web project</a></p>
</div>
<p>The post <a rel="nofollow" href="https://www.macronimous.com/blog/fluency-without-keystrokes-web-developers-in-the-ai-era/">Fluency Without Keystrokes: Web Developers in the AI Era</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/fluency-without-keystrokes-web-developers-in-the-ai-era/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
