Registration Errors

The WooCommerce Registry Conflict

⏱ 14 min

This lesson is a single, real, closed GitHub issue, worked through in detail, because it’s a genuinely useful lesson in how to debug: the reported symptom, the reporter’s own hypothesis about what was wrong, and the actual root cause the community found were three different things. If you take one habit from this lesson, take this one: when an exotic explanation and a boring one both fit the symptoms, check the boring one first.

What you'll learn in this lesson
The exact issue
GitHub issue #135 against WordPress/mcp-adapter, filed against WP 6.9.1, MCP Adapter v0.4.1, and WooCommerce 10.5.2.
The reporter's hypothesis
A suspected WooCommerce registry timing conflict, and why it seemed plausible.
What the diagnosis actually was
A missing required category argument, unrelated to WooCommerce at all.
What PR #224 actually shipped
A documentation fix, not a code fix, and why that was still the right resolution.
Prerequisites

Lesson 2 in this module, since this issue is a specific instance of the “missing required argument” category of failure covered there. WooCommerce active alongside the MCP Adapter is not required to follow along, the interesting part of this story is that it turned out not to matter.

Step 1: The report

Issue #135 was filed against a site running WordPress 6.9.1, MCP Adapter v0.4.1, and WooCommerce 10.5.2. The reporter’s plugin registered an ability on wp_abilities_api_init exactly the way the docs show, and wp_register_ability() returned null. Crucially, the reporter had confirmed doing_action( 'wp_abilities_api_init' ) returned true inside their own callback, so this wasn’t the wrong-hook mistake from Lesson 2. Something else was going on.

Step 2: The hypothesis

The reporter noticed WooCommerce was also hooking wp_abilities_api_init, and that WooCommerce’s own abilities registered successfully while theirs did not. Their conclusion, reasonable given the evidence in front of them: “the registry appears to already be initialized before our plugin’s callback runs, possibly due to WooCommerce triggering WP_Abilities_Registry::get_instance() first.” In other words, a suspected load-order conflict between WooCommerce’s own copy of the Abilities API machinery and WordPress core’s native implementation in 6.9.

A plausible hypothesis is not a confirmed diagnosis

This is worth sitting with. The reporter’s theory used real class names, a real version-specific detail (WooCommerce bundling its own abilities-api code ahead of core support landing in 6.9), and a real observed asymmetry (one plugin’s abilities registered, the other’s didn’t). Every piece of evidence was consistent with the WooCommerce-conflict theory. It was still wrong.

Step 3: What the thread actually found

Working through the issue’s comments in order tells the real story. One commenter, stefanolissa, pointed at something much more basic: “Abilities need a category and must be registered during the wp_abilities_api_categories_init action; probably, that is the problem.” Another commenter, karthikeya-io, tested it directly: adding 'category' => 'site' to the reporter’s exact reproduction code made the ability show up in wp_get_abilities() immediately. The original reporter confirmed it fixed the issue and the thread closed on that basis.

The reporter’s original reproduction code, copied from an outdated pattern that predated category becoming a required argument, simply never set one. WooCommerce’s abilities registered fine because WooCommerce’s own registrations included a category. The presence of WooCommerce in the environment was a coincidence of timing in the bug report, not a cause of the bug.

Reported as
wp_register_ability() returns NULL, suspected WooCommerce registry timing conflict.
Actually was
A missing required category argument in the reporter's own registration code.
Confirmed by
Community testing in the issue thread, reproduced and fixed by adding category directly.
Resolved by
PR #224, a documentation fix, not a code change to the adapter or to WooCommerce.

Step 4: Why the fix was documentation, not code

PR #224, “docs: document required category field for abilities,” closed the issue. It didn’t touch the adapter’s registration logic at all, because there was nothing to fix in the code: core’s Abilities API already correctly required a category and already correctly returned null when one was missing. What was actually broken was the documentation and example code developers were copying, which predated the category requirement and never mentioned it. The PR added the required field to every example, added a section explaining core’s default site and user categories plus how to register your own, and added explicit troubleshooting guidance for the null-return failure mode. As one of the commenters on the thread put it, this had been “a very common stumbling block,” common enough that the actionable fix was making sure nobody else hit it by copying the same outdated examples.

Don't blame the exotic-sounding dependency first

If you’re running WooCommerce (or any large plugin) alongside your own abilities and something isn’t registering, resist reaching for a plugin-conflict theory before you’ve confirmed the basics from Lesson 2: correct hook, no duplicate name, and every required argument present, category chief among them. The real issue #135 thread is a direct, citable example of a multi-comment debugging effort that started with a plausible-sounding conflict theory and ended with one missing string.

Test it: confirm your own category is real, not just present

A missing category returns null from wp_register_ability(). But a category that’s present in your code yet was never actually registered fails exactly the same way, since wp_register_ability_category() has to run on wp_abilities_api_categories_init before your ability’s registration on wp_abilities_api_init even attempts to use it.

// File: my-plugin.php
add_action( 'wp_abilities_api_categories_init', function () {
	wp_register_ability_category( 'my-plugin', array(
		'label' => __( 'My Plugin', 'my-plugin' ),
	) );
} );
# File: terminal, confirm the category itself exists before blaming the ability
wp eval '
$categories = wp_get_ability_categories();
echo isset( $categories["my-plugin"] ) ? "category exists" : "category NOT registered, register it on wp_abilities_api_categories_init";
'

Recap

Issue #135 looked like a WooCommerce load-order conflict and was reported that way, in good faith, with real evidence pointing that direction. The actual root cause, found by the community and confirmed by testing, was a missing category argument, unrelated to WooCommerce entirely. PR #224 resolved it by fixing the documentation and examples that had let the mistake propagate, not by changing any registration code. When you’re debugging a registration failure that seems to involve another large plugin, confirm the boring causes from Lesson 2, especially category, before investing time in a theory about plugin load order.

Resources & further reading

← Why wp_register_ability() Returns NULL Fixing Hook-Timing and Load-Order Errors →