Commit 9c79cccc99f for woocommerce
commit 9c79cccc99fba1489a103881b37e64d5cc72b0ce
Author: Ricardo Sawir <37329575+sawirricardo@users.noreply.github.com>
Date: Fri Jul 3 01:44:59 2026 +0700
Fix duplicate "Password Changed" admin email on customer password reset (#66145)
* Fix duplicate Password Changed admin email on customer password reset
WC_Shortcode_My_Account::reset_password() added an after_password_reset
action in 10.9.0 (#64380). WordPress core hooks wp_password_change_notification()
onto that action, so it fired alongside the pre-existing direct call, sending
the admin two identical notifications.
Detach core's handler around the do_action() and restore it, leaving
WooCommerce's own filter-guarded direct call as the single notification source.
Bug introduced in PR #64380.
* Restore core notification hook in finally block
Ensures wp_password_change_notification is re-attached even if a third-party
after_password_reset listener throws.
* Add changefile(s) from automation for the following project(s): woocommerce
* Delete plugins/woocommerce/changelog/66145-fix-66103-duplicate-password-changed-email
* Remove "Comment:" from changelog
diff --git a/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email b/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email
new file mode 100644
index 00000000000..7b6de66be48
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66103-duplicate-password-changed-email
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix duplicate "Password Changed" admin notification email sent when a customer resets their password.
diff --git a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
index 4dbb81a8d56..c5dda9a9102 100644
--- a/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
+++ b/plugins/woocommerce/includes/shortcodes/class-wc-shortcode-my-account.php
@@ -387,16 +387,31 @@ class WC_Shortcode_My_Account {
// The temporary-password notice is gone for good now, so drop its resend rate-limit timestamp.
delete_user_meta( $user->ID, WC_Form_Handler::SET_PASSWORD_RESEND_META );
- /**
- * Fires after the user's password has been reset via WooCommerce.
- *
- * This provides parity with WordPress core's reset_password() function.
- *
- * @since 10.9.0
- * @param WP_User $user The user.
- * @param string $new_pass New user password in plaintext.
- */
- do_action( 'after_password_reset', $user, $new_pass );
+ // WordPress core hooks wp_password_change_notification() onto after_password_reset. Detach it around
+ // the action so it doesn't duplicate the notification WooCommerce sends directly below (guarded by the
+ // woocommerce_disable_password_change_notification filter), then restore it to its original priority.
+ $core_notification_priority = has_action( 'after_password_reset', 'wp_password_change_notification' );
+ if ( false !== $core_notification_priority ) {
+ remove_action( 'after_password_reset', 'wp_password_change_notification', $core_notification_priority );
+ }
+
+ try {
+ /**
+ * Fires after the user's password has been reset via WooCommerce.
+ *
+ * This provides parity with WordPress core's reset_password() function.
+ *
+ * @since 10.9.0
+ * @param WP_User $user The user.
+ * @param string $new_pass New user password in plaintext.
+ */
+ do_action( 'after_password_reset', $user, $new_pass );
+ } finally {
+ if ( false !== $core_notification_priority ) {
+ add_action( 'after_password_reset', 'wp_password_change_notification', $core_notification_priority );
+ }
+ }
+
self::set_reset_password_cookie();
wc_set_customer_auth_cookie( $user->ID );