What AI Still Gets Wrong When Building Real Websites

Quick Summary

  • AI coding mistakes cluster in hidden platform conventions, environment differences the agent can't see, and judgment calls that look like bugs.
  • Rebuild bugs included wp_update_post() stripping backslashes, sanitize_title as a REST sanitize_callback, $more and $id clashing with globals, and plugin interference.
  • Catch AI mistakes with dull, independent checks: byte-for-byte comparisons, WP_DEBUG renders, real-browser screenshots on the real plugin stack, edge-input tests and human review.

Four posts on my blog lost their backslashes on the way into the database, and nothing about them looked broken. A code block that says n where it should say \n still renders, still highlights and still copies. It just doesn’t work. A lot of AI mistakes in web development look exactly like that.

An AI coding agent introduced that bug during the rebuild of this site. The same agent caught it, with a byte-for-byte comparison of every code block after saving. Both halves of that are typical of working with AI today. The mistakes aren’t random. They cluster in three places: where the platform has conventions that aren’t visible in the code in front of you, where the running environment differs from the repo, and where the right answer is a judgment call.

Here is each failure type, with real examples from the rebuild: what happened, why an AI misses it, and how to catch it.

Hidden platform conventions behind AI mistakes in web development

WordPress is two decades of backward compatibility. Plenty of its behavior is documented but surprising, and AI-generated code tends to follow what a function’s name suggests rather than what it actually does.

wp_update_post() quietly unslashes

What happened: a script saved rewritten post content with wp_update_post(). In 4 posts, backslashes inside code blocks were stripped, so \n became n.

Why AI misses it: WordPress adds slashes to incoming request data, and its post-saving functions expect that, so they strip one layer of backslashes before writing. Pass a clean string and you lose characters. The call looks right, reads right, and works perfectly for any content without a backslash in it, which is most prose.

How to catch it: compare the output with the input, not with your expectations. Here, every code block was compared byte for byte with the original after saving, and exactly those 4 posts failed. The fix is one function call:

PHP
// Broken: wp_update_post() unslashes its input, so "\n" is saved as "n".
wp_update_post( [
	'ID'           => $post_id,
	'post_content' => $new_content,
] );

// Fixed: add the slashes WordPress is about to remove.
wp_update_post( wp_slash( [
	'ID'           => $post_id,
	'post_content' => $new_content,
] ) );

The reference entry for wp_slash() is worth a read if you write migration scripts.

A REST callback that receives more than you think

What happened: a REST route for the blog’s live search used sanitize_title directly as the sanitize_callback for a category parameter. An empty value produced a 500 error.

Why AI misses it: WordPress calls a sanitize callback with three arguments: the value, the request object and the parameter name. sanitize_title() has an optional second parameter, a fallback title, used when the result is empty. So an empty category came back as the request object itself, and the code that expected a string broke. Passing a core function name as a callback is a common pattern and works with single-argument functions like sanitize_text_field(), which is exactly why it looks safe everywhere.

How to catch it: test edge inputs (empty, missing, malformed), not only the happy path. The symptom here was loud, but the cause sat two layers down. The fix is a wrapper that passes one argument:

PHP
add_action( 'rest_api_init', function () {
	register_rest_route( 'nmh/v1', '/blog', [
		'methods'             => 'GET',
		'permission_callback' => '__return_true',
		'callback'            => 'nmh_blog_rest',
		'args'                => [
			'cat' => [
				'type'              => 'string',
				'default'           => '',
				// Not 'sanitize_callback' => 'sanitize_title': REST passes
				// ( $value, $request, $param ), and $request would become the fallback.
				'sanitize_callback' => fn( $value ) => sanitize_title( (string) $value ),
			],
		],
	] );
} );

Template variables that are secretly globals

What happened: templates used variables named $more and $id. Single posts threw a fatal error until they were renamed.

Why AI misses it: WordPress template files run in global scope, and template parts declare $id global, so a “local” variable in a template can overwrite a WordPress global. An agent writing templates tends to treat them like isolated view files, where a variable name is only a variable name.

How to catch it: render every page type with WP_DEBUG on, not just the home page. Single posts, archives, search results and the 404 page each run different templates, and each can fail on its own.

The environment it can’t see

An agent working in your repo sees your code. It doesn’t see which plugins are active, how they’re configured, or what they do to the page after your theme has finished. On my rebuild, three of the most visible problems lived there.

  • A page builder that was still in charge. Elementor Pro’s Theme Builder was still active and silently replaced the new theme’s header and footer.
  • A cache that held back the JavaScript. A caching plugin delayed all JavaScript until the first user interaction, which broke the loader and the animations.
  • An optimizer that damaged images. An image optimization plugin on its most aggressive setting had re-compressed images that were already optimized, and photos looked smeared.

Why AI misses it: from inside the theme folder, all three look like correct code, because the code was correct. Plugin settings live in the database, and their effects happen at render time.

How to catch it: look at the real page with the real plugin stack, compared against the design. The fixes were human-sized: switch off Elementor Pro and the caching plugin; restore the original images from the optimizer’s backups, set it to lossless, and raise WordPress’s resize quality from 82 to 90. Before debugging theme code, list every active plugin that touches the front end. It’s the cheapest check there is. Better still, put that list in the agent’s brief and mark the ones that change output: page builders, caching, image optimization, SEO.

Tests that share the code’s assumptions

When the same agent writes the code and the tests, the tests can inherit the code’s blind spots. A test written by someone who believes wp_update_post() saves exactly what it’s given will confirm that the post saved, not that the backslashes survived.

The checks I trust most compare against something independent of the code: the original bytes of every code block, the static design in a screenshot, a real HTTP status code for every redirect.

Motion is another place where tests and reality drift apart. On a site with smooth scrolling and scroll-triggered reveals, content can still be moving when a scripted click fires. Browser tests on that kind of page should wait for the page to settle before they act, or they end up testing the animation’s timing instead of the feature.

Judgment calls dressed up as bugs

Some problems have a quick fix that is technically correct and still wrong.

What happened: when the photos looked smeared, the agent’s first proposal was to force the browser to always load the largest image. It treated the symptom, and it would have made every page heavier on every phone, which is exactly what responsive images exist to prevent. I rejected it and asked for standard responsive-image practice instead. Together with the quality fix above, that was enough.

Why AI misses it: it optimizes for the symptom it was given. “Photos look blurry” has a fast answer, and nothing in the prompt says that page weight on a phone matters more than one reported symptom. The same goes for choices like whether long lines in a code block wrap or scroll sideways. Neither is a bug, and an agent will pick one without asking.

How to catch it: a person who knows what the site is for, reading the proposal before it becomes code. It also helps to change the question. Instead of “fix the blurry photos”, ask for the likely causes and what each fix would cost. Asking for options and trade-offs instead of a fix changes the conversation, which is a big part of why AI coding is about thinking differently, not typing faster.

A catch-net of dull checks for AI mistakes in web development

None of these bugs was exotic. Each could have shipped if the only check had been “the agent says it’s done”. The catch-net is a small set of repeatable checks, none of them clever:

  • Byte-for-byte comparisons for anything that must not change, such as code in posts.
  • Debug renders of every template with WP_DEBUG on.
  • Real-browser checks: screenshots against the design and scripted interactions, on the real plugin stack.
  • Edge-input tests for anything that takes a parameter.
  • A human reading proposals and diffs and making the judgment calls.

The full story of the build is in I built a WordPress theme with AI, and the design-side version of this problem is in the difference between an AI-generated website and a well-designed one.

An agent wrote the code for this rebuild. What changed for me is where I look for mistakes: in the platform’s conventions, in the environment, and in any fix that quietly decides something on my behalf.

Share on X (opens in a new tab) Share on LinkedIn (opens in a new tab) Share on Facebook (opens in a new tab)

ChatGPT (opens in a new tab) Claude (opens in a new tab) Gemini (opens in a new tab) Perplexity (opens in a new tab) Grok (opens in a new tab)

More Insights

See More