AI generated code you can’t unhook later

add_filter( 'the_content', function ( $content ) {
	return str_replace( 'foo', 'bar', $content );
} );

That is the default shape of AI-generated code in WordPress. An agent writes the filter, it passes the first test. Then someone needs to turn it off for one page type or replace it with a plugin’s own filter, and there is no clean way out, because remove_filter() has to be given the exact callback being removed and a closure has no name to point at. Two closures with identical bodies are still two different functions, so retyping the same code matches nothing. The remove_filter() reference says it plainly: there is no easy way to target a hook callback that relies on an anonymous function.

The gap between how cheap that was to write and how hard it is to undo is the point of this post.

The fixes that make it worse

The workaround I run into goes like this: loop over $GLOBALS[‘wp_filter’][‘the_content’]->callbacks, find the entry whose function is a Closure instance, and splice it out. I understand the appeal, and I have written hackier things under deadline. It still fails quietly. You can’t be sure the closure you removed was yours, and the day core reorganizes how it stores hooks, the loop just stops doing anything. remove_all_filters( ‘the_content’ ) is the blunt version of the same idea, and on a store with a few plugins, the content filters usually have several owners you never installed.

Named callbacks, and a review rule

The version I ship names the callback and prefixes it, so any other piece of code can unregister it later:

add_filter( 'the_content', 'bbioon_replace_bar', 20 );

function bbioon_replace_bar( $content ) {
	return str_replace( 'foo', 'bar', $content );
}

// From a mu-plugin, another plugin, or a child theme:
remove_filter( 'the_content', 'bbioon_replace_bar', 20 );

One detail bites people: remove_filter() must be called with the same priority the callback was added at, priority 20 here, or it silently does nothing. A static class method works the same way with array( BBIOON_Content::class, ‘on_content’ ), which is what I reach for once a site has more than a few of these. My rule in review: closures are fine inside a function’s own scope, but anything attached to a public hook gets a named callback, because public hooks are the API plugins use to cooperate with each other. AI-generated code keeps treating them as private wiring, and the metacognition questions I ask of agent output catch most of it before merge.

If you have a client site where this already happened, that is work I take on: find the nameless hooks, give them names and priorities, and add a comment about why they exist, so the next person can switch any of them off. Happy to look if that’s your situation.

Cheap creation, expensive ownership

The prompt for this post was Clara Chong’s write-up of the AI Engineer Singapore talks, where Jimmy Lai of Vercel put the tradeoff in one line:

AI has made creation cheap, but ownership more expensive.

WordPress makes the ownership side visible, because cheap code rarely stays in a prototype branch here. A client sees a working demo, approves it, and it is on production the same week. From that day it has to survive PHP upgrades, plugin updates, and eventually a developer who never saw the prompt that produced it. Chong’s other point, that attention is now the scarce resource, shows up at update time: every surviving snippet spends a bit of it forever. That is why I delete more than I keep. If a snippet has no owner, no removal path, and no comment saying why it exists, it does not ship. The debt AI code leaves behind grows at that decision point.

Who pays for code you can’t unhook

What I still don’t know is whether this problem fixes itself. Agents could pick up the naming convention the way they picked up doc blocks, once enough repos and review comments reward it. Then this post ages badly, which I would accept. Or the ecosystem could move toward declaring hooks in a machine-readable way, so anything can unregister them without string matching. The question matters because of who pays in the meantime. Whoever generated the code feels none of the ownership cost, and whoever inherits the site feels all of it. Until that split closes, rejecting nameless hooks in review is the cheapest thing I know to do about it.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Comment