Commit 7105b03a41e for woocommerce

commit 7105b03a41ec4c7c84b51d524b7d250164f95a37
Author: Vladimir Reznichenko <kalessil@gmail.com>
Date:   Mon Jun 1 16:56:51 2026 +0200

    [dev] Update .ai/skills/woocommerce-performance/options-cache-priming.md (#65441)

diff --git a/.ai/skills/woocommerce-performance/options-cache-priming.md b/.ai/skills/woocommerce-performance/options-cache-priming.md
index 70b3d69593a..d63925216ce 100644
--- a/.ai/skills/woocommerce-performance/options-cache-priming.md
+++ b/.ai/skills/woocommerce-performance/options-cache-priming.md
@@ -77,6 +77,40 @@ Guard with `! empty()` when the list is dynamically built and may be empty. When

 ---

+### 4. Transient names passed to `wp_prime_option_caches()` — unsafe under persistent object cache
+
+**Anti-pattern:** Passing `_transient_*`, `_transient_timeout_*`, `_site_transient_*`, or `_site_transient_timeout_*` option names to `wp_prime_option_caches()`.
+
+**Why it is wrong:** When a persistent object cache is active, WordPress stores transients in the object cache under the `transient` group — not as rows in `wp_options`. `wp_prime_option_caches()` reads from the options table. On a persistent-cache site the named rows never exist, so each prime call records every transient name as a `notoptions` entry. Those entries persist indefinitely: the corresponding `wp_options` rows are never created for transients stored exclusively in the object cache, so the normal invalidation path (`add_option` / `update_option`) never fires. The `notoptions` cache grows by two entries per transient name per call (`_transient_<name>` + `_transient_timeout_<name>`). On backends where `notoptions` resolves to a single cache key read on every request (observed with sharded Redis), this growth increases per-request retrieval cost over time.
+
+**Correct pattern:**
+
+```php
+// Transients are stored in the options table only when no persistent object cache is active.
+// Passing transient names to wp_prime_option_caches() under a persistent object cache
+// records them in the notoptions negative-cache indefinitely, since those rows are never
+// created in wp_options. Sites with a persistent cache already retrieve transients from the
+// object cache in O(1) — no priming is needed or beneficial.
+if ( ! wp_using_ext_object_cache() ) {
+    wp_prime_option_caches( $transient_option_names );
+}
+```
+
+`wp_using_ext_object_cache()` is the same guard WordPress itself uses inside `get_transient()` and `set_transient()` to switch between the options table and the object cache. Sites without a persistent cache keep the existing batching behaviour. Sites with one already retrieve transients from the object cache directly — skipping the prime loses nothing.
+
+**Mixed key list:** If the array passed to `wp_prime_option_caches()` mixes regular option names with transient names, split the call: prime the regular option names unconditionally; prime the transient names only under `! wp_using_ext_object_cache()`. Wrapping the entire call in the guard would silently drop the regular option priming on persistent-cache sites.
+
+```php
+wp_prime_option_caches( $regular_option_names );
+if ( ! wp_using_ext_object_cache() ) {
+    wp_prime_option_caches( $transient_option_names );
+}
+```
+
+**Audit rule:** Any call to `wp_prime_option_caches()` whose key list contains names beginning with `_transient_`, `_transient_timeout_`, `_site_transient_`, or `_site_transient_timeout_` must be guarded with `! wp_using_ext_object_cache()`.
+
+---
+
 ## Notes

 `wp_prime_option_caches()` is a stable public WordPress function (no underscore prefix), available since WP 6.4. WooCommerce's minimum supported WordPress version guarantees its presence — no `is_callable()` guard is needed.
diff --git a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
index 3e9b88d9122..10082c363aa 100644
--- a/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/class-wc-product-variable-data-store-cpt.php
@@ -128,14 +128,7 @@ class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT imple
 	 * @since 3.0.0
 	 */
 	protected function read_product_data( &$product ) {
-		// Only prime the transient option caches when there is no persistent object
-		// cache. When one is active, get_transient() reads these values from the
-		// 'transient' cache group and never from wp_options, so priming the option
-		// names cannot help. Instead each missing name is recorded in core's
-		// 'notoptions' cache, and because the options never exist the entries are
-		// never cleared, so notoptions grows by four per variable product across the
-		// catalog. WC_Order::needs_processing() stopped using transients for the same
-		// reason in 10.8.0.
+		// Prime caches to reduce future queries.
 		if ( ! wp_using_ext_object_cache() ) {
 			$product_id = $product->get_id();
 			wp_prime_option_caches(