Commit 64d3ec832ce for woocommerce

commit 64d3ec832cec31d1b5c64528b9dc6ba8072e56ec
Author: Chris Huber <chubes@extrachill.com>
Date:   Thu Jul 9 13:24:07 2026 -0400

    Fix deferred product transient shutdown order (#66168)

    * Fix deferred product transient shutdown order

    * Match deferred transient shutdown removal priority

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Add transient deferrer shutdown ordering coverage

    * Add changefile(s) from automation for the following project(s): woocommerce

    * Update tests

    ---------

    Co-authored-by: Jorge Torres <jorge.torres@automattic.com>

diff --git a/plugins/woocommerce/changelog/66168-fix-65686-transient-deferrer-shutdown-order b/plugins/woocommerce/changelog/66168-fix-65686-transient-deferrer-shutdown-order
new file mode 100644
index 00000000000..8655d9d50a4
--- /dev/null
+++ b/plugins/woocommerce/changelog/66168-fix-65686-transient-deferrer-shutdown-order
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix deferred product transient shutdown ordering before parent product sync.
diff --git a/plugins/woocommerce/src/Internal/Caches/ProductTransientsDeferrer.php b/plugins/woocommerce/src/Internal/Caches/ProductTransientsDeferrer.php
index 474f6333c05..fe8840c225e 100644
--- a/plugins/woocommerce/src/Internal/Caches/ProductTransientsDeferrer.php
+++ b/plugins/woocommerce/src/Internal/Caches/ProductTransientsDeferrer.php
@@ -13,6 +13,14 @@ use Automattic\WooCommerce\Internal\Utilities\ProductUtil;
  */
 class ProductTransientsDeferrer {

+	/**
+	 * Priority for the shutdown safety-net hook.
+	 *
+	 * This must run before WC_Post_Data::do_deferred_product_sync() at priority 10
+	 * so stale product transients are cleared before parent product sync reads them.
+	 */
+	private const SHUTDOWN_HOOK_PRIORITY = 0;
+
 	/**
 	 * The product utility instance.
 	 *
@@ -54,7 +62,7 @@ class ProductTransientsDeferrer {
 	public function start_deferring(): void {
 		++$this->deferral_level;
 		if ( 1 === $this->deferral_level ) {
-			add_action( 'shutdown', array( $this, 'handle_shutdown' ) );
+			add_action( 'shutdown', array( $this, 'handle_shutdown' ), self::SHUTDOWN_HOOK_PRIORITY );
 		}
 	}

@@ -72,7 +80,7 @@ class ProductTransientsDeferrer {

 		--$this->deferral_level;
 		if ( 0 === $this->deferral_level ) {
-			remove_action( 'shutdown', array( $this, 'handle_shutdown' ) );
+			remove_action( 'shutdown', array( $this, 'handle_shutdown' ), self::SHUTDOWN_HOOK_PRIORITY );
 			$this->flush();
 		}
 	}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Caches/ProductTransientsDeferrerTest.php b/plugins/woocommerce/tests/php/src/Internal/Caches/ProductTransientsDeferrerTest.php
index 61fcc305253..e4ceaa10be9 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Caches/ProductTransientsDeferrerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Caches/ProductTransientsDeferrerTest.php
@@ -11,6 +11,29 @@ use Automattic\WooCommerce\Internal\Caches\ProductTransientsDeferrer;
  */
 class ProductTransientsDeferrerTest extends \WC_Unit_Test_Case {

+	/**
+	 * @testdox Shutdown fallback flushes product transients before deferred parent product sync.
+	 */
+	public function test_shutdown_fallback_runs_before_deferred_product_sync() {
+		$deferrer = wc_get_container()->get( ProductTransientsDeferrer::class );
+
+		try {
+			$deferrer->start_deferring();
+
+			$sync_priority = has_action( 'shutdown', array( 'WC_Post_Data', 'do_deferred_product_sync' ) );
+			$priority      = has_action( 'shutdown', array( $deferrer, 'handle_shutdown' ) );
+
+			$this->assertNotFalse( $priority );
+			$this->assertNotFalse( $sync_priority );
+			$this->assertLessThan( $sync_priority, $priority );
+
+			$deferrer->stop_deferring();
+			$this->assertFalse( has_action( 'shutdown', array( $deferrer, 'handle_shutdown' ) ) );
+		} finally {
+			$deferrer->stop_deferring();
+		}
+	}
+
 	/**
 	 * @testdox Product transient deferral coalesces repeated deletions until the outermost deferral ends.
 	 */