MySQL 2014 "Commands out of sync" during WooCommerce BatchProcessingController shutdown — reproducible 0 -> 2014 transition
Summary
I am experiencing a reproducible MySQL error:
Commands out of sync; you can't run this command now
with MySQL client error number 2014.
The error consistently appears during WooCommerce's BatchProcessingController shutdown processing.
I instrumented both the WordPress $wpdb query path and the WordPress shutdown hook on a live production ecommerce store. The most important finding is that the WooCommerce BatchProcessingController shutdown callback is repeatedly observed as the exact boundary where the mysqli connection transitions from a healthy state (errno=0) to errno=2014.
The recurring sequence is:
Action Scheduler query
|
| errno = 0
v
WooCommerce BatchProcessingController shutdown callback
|
v
get_option('wc_pending_batch_processes')
|
| errno = 2014
v
Commands out of sync; you can't run this command now
I am opening this issue to provide an independent production reproduction with detailed diagnostic data and to ask whether the WooCommerce / Action Scheduler maintainers can identify the exact operation that causes the mysqli connection to enter the 2014 state.
Environment
The affected site is a live production WooCommerce ecommerce store.
Current environment:
- WordPress: 7.1
- WooCommerce: 11.0.1
- Action Scheduler: 4.0.0
- PHP: 8.4.23
- mysqlnd: 8.4.23
- MySQL: 8.0.46
- Web server / SAPI: LiteSpeed
- HPOS: DISABLED
- Database tables: InnoDB
All database tables on the affected site use InnoDB.
The issue reproduces with HPOS disabled and does not depend on MyISAM tables.
The error has been observed on multiple types of requests, including:
- product pages
- product category pages
- product tag pages
/shop/
- filtered shop requests
- PJAX filter requests
- admin / Elementor requests
Therefore, the problem is not limited to one URL or one particular frontend page.
Initial investigation
I initially created a lightweight $wpdb diagnostic logger.
It records:
- mysqli errno
- mysqli error string
- SQLSTATE
- server/client information
mysqli_more_results()
mysqli_field_count()
$wpdb->last_error
$wpdb->last_query
$wpdb->func_call
$wpdb->num_queries
- the previous 8 successful
$wpdb queries
- request context
- backtrace
The logger is strictly observational.
It does not:
- execute additional SQL for repair
- call
mysqli_next_result()
- call
mysqli_free_result()
- reconnect
- repair the database connection
- modify queries
- modify database data
- enable
SAVEQUERIES
What the initial logger showed
Across many occurrences, the first $wpdb query that actually reported error 2014 was consistently a shutdown-related query.
The most common sequence was:
Immediately preceding successful query
SELECT a.action_id
FROM wp_actionscheduler_actions a
WHERE 1=1
AND a.hook='wc_schedule_pending_batch_processes'
AND a.status IN ('in-progress', 'pending')
LIMIT 0, 1
Immediately following query
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
The second query then reported:
errno: 2014
error: Commands out of sync; you can't run this command now
sqlstate: HY000
The initial interpretation was that these queries might be victims rather than the actual cause, because the original logger only observes queries that pass through WordPress $wpdb.
Targeted shutdown instrumentation
I then built a targeted observational wrapper around the WordPress shutdown WP_Hook.
The wrapper observes the registered shutdown callbacks and records the mysqli connection state immediately before and immediately after each observed callback.
It does not intentionally change:
- callback priority
- callback ID
- callback ordering
- callback arguments
accepted_args
- SQL
- database contents
No connection repair or result handling is performed.
In particular, the diagnostic code does NOT call:
mysqli_next_result()
mysqli_free_result()
- reconnect
- corrective SQL
The purpose is solely to observe the connection state across callback boundaries.
Repeated 0 -> 2014 transition
The targeted observer repeatedly produced results like:
Culprit callback:
Closure @ .../woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php:121
Priority on shutdown: 10
errno before callback: 0
errno after callback: 2014
more_results before callback: no
more_results after callback: no
field_count before callback: 1
field_count after callback: 1
This was reproduced across multiple independent production requests.
Examples include:
/product-category/dob/dob-nocap/
/product-category/strip-led/
/product-category/full-spectrum-uv/
/shop/?filter_brand=xgd&_pjax=.wd-page-content
/shop/?filter_brand=xgd&filter_color=...&_pjax=.wd-page-content
/product/...
/wp-admin/post.php?post=14&action=elementor
The request URLs vary, but the same WooCommerce shutdown callback remains the observed boundary.
Most importantly, the observed transition is repeatedly:
errno=0
->
BatchProcessingController shutdown callback
->
errno=2014
and not simply:
Relevant WooCommerce callback
The callback identified by the instrumentation is the anonymous shutdown closure created by:
BatchProcessingController::__construct()
at approximately line 121 of the installed WooCommerce 11.0.1 code.
The relevant stack path captured during the first observed 2014 query is:
shutdown_action_hook
-> do_action('shutdown')
-> WP_Hook->do_action()
-> BatchProcessingController->{closure}
-> BatchProcessingController->remove_or_retry_failed_processors()
-> BatchProcessingController->get_enqueued_processors()
-> get_option()
-> wpdb->get_row()
-> wpdb->query()
The first observed 2014 query is:
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
The call stack points through:
BatchProcessingController->get_enqueued_processors()
and:
BatchProcessingController->remove_or_retry_failed_processors()
Exact observed query sequence
The latest diagnostic layer also records:
transition pattern:
ACTION_SCHEDULER_THEN_PENDING_OPTION
Immediately preceding successful $wpdb query:
SELECT a.action_id
FROM wp_actionscheduler_actions a
WHERE 1=1
AND a.hook='wc_schedule_pending_batch_processes'
AND a.status IN ('in-progress', 'pending')
LIMIT 0, 1
First observed 2014 query:
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
Connection state:
Before BatchProcessingController shutdown callback:
errno = 0
After BatchProcessingController shutdown callback:
errno = 2014
This pattern has now been observed repeatedly.
Representative diagnostic output
A representative capture looks like:
logger V10 — SHUTDOWN CULPRIT DETECTED
Culprit callback:
Closure @ .../woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php:121
Priority on shutdown: 10
errno before callback: 0
errno after callback: 2014
more_results before callback: no
more_results after callback: no
field_count before callback: 1
field_count after callback: 1
thread_id: 66338
error:
Commands out of sync; you can't run this command now
last_query (wpdb):
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
observed 2014 query inside callback:
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
immediate previous WPDB query:
SELECT a.action_id
FROM wp_actionscheduler_actions a
WHERE 1=1
AND a.hook='wc_schedule_pending_batch_processes'
AND a.status IN ('in-progress', 'pending')
LIMIT 0, 1
transition pattern:
ACTION_SCHEDULER_THEN_PENDING_OPTION
This exact query pattern and callback boundary have been observed more than once.
---
## Important interpretation
I want to be careful not to overstate what has been proven.
The instrumentation demonstrates very strongly that the WooCommerce `BatchProcessingController` shutdown callback is the reproducible boundary across which the mysqli connection changes from `errno=0` to `errno=2014`.
However, this does not by itself prove that line 121 itself is the low-level root cause.
In particular, the current evidence is compatible with a sequence such as:
BatchProcessingController shutdown callback
->
Action Scheduler query
->
some internal DB/result/connection state transition
->
get_option('wc_pending_batch_processes')
->
errno=2014
Therefore:
* the shutdown callback is consistently involved in the failure boundary;
* `wc_pending_batch_processes` is the first query that exposes the bad state;
* the exact lower-level operation that causes the connection to enter the 2014 state may still require source-level investigation.
---
## Diagnostic refinement
There was an important subtle issue in the first shutdown observer implementation.
If error 2014 was observed by the normal `$wpdb` `query` filter while an observed shutdown callback was still executing, the original V4 behavior could mark the request as already `triggered` immediately.
That could happen before the outer shutdown wrapper reached its post-callback check, preventing callback-level attribution from being recorded.
The diagnostic logic was therefore changed so that a 2014 observed inside an observed shutdown callback is temporarily marked as pending rather than immediately finalizing the diagnostic.
The flow is now:
-
callback starts
-
mysqli state before callback is recorded
-
callback executes normally
-
if a $wpdb query sees 2014, the observation remains pending
-
callback reaches its normal completion / finally boundary
-
mysqli state after callback is recorded
-
a callback is attributed only when:
before != 2014
after == 2014
This refinement is what produced the repeated 0 -> 2014 boundary attribution above.
The diagnostic additionally records:
* the first observed 2014 query
* the immediately preceding successful `$wpdb` query
* the stack trace at the first 2014 observation
* whether the sequence matches the Action Scheduler -> `wc_pending_batch_processes` pattern
---
## `mysqli_more_results()` observation
Another potentially useful detail is that the captured cases do not simply show:
mysqli_more_results() == yes
more_results before callback: no
more_results after callback: no
This does not rule out an earlier result-state problem, but it means that `mysqli_more_results()` returning `yes` is not, by itself, an explanation for the observed failures.
---
## Relationship to Action Scheduler
The immediately preceding successful query is an Action Scheduler query against:
wp_actionscheduler_actions
wc_schedule_pending_batch_processes
The subsequent query is WooCommerce's read of:
wc_pending_batch_processes
The important distinction is that the Action Scheduler query itself does not report error 2014 in the captured diagnostic state.
Instead, the subsequent `$wpdb` operation is the first operation that exposes the bad state.
This raises the question of whether the underlying connection/result state transition occurs during or immediately after the Action Scheduler operation, rather than being caused by the final `wc_pending_batch_processes` query itself.
---
## Relation to PR #65406
I also reviewed PR #65406:
[https://github.com/woocommerce/woocommerce/pull/65406](https://github.com/woocommerce/woocommerce/pull/65406)
The proposed approach adds lightweight database-health checks around the `BatchProcessingController` shutdown cleanup path in order to prevent additional database work from continuing after the connection has become unhealthy.
That is relevant as a defensive measure because it could prevent a shutdown error cascade.
However, I believe two separate questions should be distinguished:
1. How should WooCommerce safely prevent the cascade once `errno=2014` has already occurred?
2. What exact operation causes the connection/result state to transition into `errno=2014` in the first place?
The diagnostic data in this issue is primarily intended to help investigate the second question.
I have intentionally not applied the proposed workaround to the production store because I do not want to modify WooCommerce's database/result behavior without understanding the underlying cause.
---
## Production impact
The affected production site is currently functioning normally from the user's perspective.
Normal ecommerce functionality is working, including:
* product browsing
* category browsing
* shop/filtering
* cart
* checkout
* order functionality
The error is being observed at the database level during shutdown/batch-processing activity rather than as an obvious frontend failure.
For that reason, I have avoided introducing a speculative production-side workaround.
---
## What the diagnostic does NOT do
The diagnostic implementation does not:
* execute additional SQL for repair
* drain MySQL result sets
* call `mysqli_next_result()`
* call `mysqli_free_result()`
* reconnect the database
* repair the connection
* modify database records
* modify Action Scheduler records
* modify WooCommerce data
* modify query contents
* enable `SAVEQUERIES`
* disable Action Scheduler
* disable WooCommerce batch processing
* intentionally change callback priority
* intentionally change callback ordering
* intentionally change callback arguments
It is strictly intended to observe and document the failure.
---
## Current conclusion
At this point, I can repeatedly demonstrate the following on a live production store:
Action Scheduler query
[errno=0]
->
BatchProcessingController shutdown callback
->
get_option('wc_pending_batch_processes')
[first observed errno=2014]
BatchProcessingController shutdown callback
errno before:
0
errno after:
2014
WordPress 7.1
WooCommerce 11.0.1
Action Scheduler 4.0.0
PHP 8.4.23
mysqlnd 8.4.23
MySQL 8.0.46
LiteSpeed
HPOS disabled
InnoDB tables
Persistent database connections disabled
The same failure pattern occurs across multiple unrelated frontend and administrative requests.
The investigation has therefore narrowed the problem from "somewhere in the request" to the WooCommerce `BatchProcessingController` shutdown path, with repeated direct observation of the 0 -> 2014 transition and the exact Action Scheduler -> `wc_pending_batch_processes` query sequence.
---
## Request for investigation
Could the WooCommerce / Action Scheduler maintainers please investigate whether this shutdown path can cause or expose a mysqli connection/result-state transition that leads to error 2014 on the subsequent `wc_pending_batch_processes` query?
In particular, I would appreciate investigation of:
1. What exact database operation occurs between the successful Action Scheduler query and the failing `wc_pending_batch_processes` query?
2. Whether `ActionScheduler_DBStore::query_actions()` can leave any result/connection state that is incompatible with the following `$wpdb` operation.
3. Whether the `BatchProcessingController` shutdown cleanup path itself can trigger this transition.
4. Whether this behavior is related to MySQL 8.0 / mysqlnd 8.4.
5. Whether there is a known interaction between Action Scheduler 4.0.0 and the WordPress `$wpdb` connection in this scenario.
6. Whether a defensive guard such as the approach explored in PR #65406 should be added independently of the underlying root-cause fix.
I am providing this as an independent production reproduction and detailed diagnostic data.
I have deliberately avoided modifying WooCommerce core or applying a speculative database/result workaround on the live store.
I can provide the complete diagnostic implementation, additional raw logs, full stack traces, and further reproduction data if useful.
Thank you.
MySQL 2014 "Commands out of sync" during WooCommerce BatchProcessingController shutdown — reproducible 0 -> 2014 transition
Summary
I am experiencing a reproducible MySQL error:
Commands out of sync; you can't run this command now
with MySQL client error number 2014.
The error consistently appears during WooCommerce's BatchProcessingController shutdown processing.
I instrumented both the WordPress $wpdb query path and the WordPress
shutdownhook on a live production ecommerce store. The most important finding is that the WooCommerce BatchProcessingController shutdown callback is repeatedly observed as the exact boundary where the mysqli connection transitions from a healthy state (errno=0) toerrno=2014.The recurring sequence is:
I am opening this issue to provide an independent production reproduction with detailed diagnostic data and to ask whether the WooCommerce / Action Scheduler maintainers can identify the exact operation that causes the mysqli connection to enter the 2014 state.
Environment
The affected site is a live production WooCommerce ecommerce store.
Current environment:
All database tables on the affected site use InnoDB.
The issue reproduces with HPOS disabled and does not depend on MyISAM tables.
The error has been observed on multiple types of requests, including:
/shop/Therefore, the problem is not limited to one URL or one particular frontend page.
Initial investigation
I initially created a lightweight
$wpdbdiagnostic logger.It records:
mysqli_more_results()mysqli_field_count()$wpdb->last_error$wpdb->last_query$wpdb->func_call$wpdb->num_queries$wpdbqueriesThe logger is strictly observational.
It does not:
mysqli_next_result()mysqli_free_result()SAVEQUERIESWhat the initial logger showed
Across many occurrences, the first
$wpdbquery that actually reported error 2014 was consistently a shutdown-related query.The most common sequence was:
Immediately preceding successful query
Immediately following query
The second query then reported:
The initial interpretation was that these queries might be victims rather than the actual cause, because the original logger only observes queries that pass through WordPress
$wpdb.Targeted shutdown instrumentation
I then built a targeted observational wrapper around the WordPress
shutdownWP_Hook.The wrapper observes the registered shutdown callbacks and records the mysqli connection state immediately before and immediately after each observed callback.
It does not intentionally change:
accepted_argsNo connection repair or result handling is performed.
In particular, the diagnostic code does NOT call:
mysqli_next_result()mysqli_free_result()The purpose is solely to observe the connection state across callback boundaries.
Repeated 0 -> 2014 transition
The targeted observer repeatedly produced results like:
This was reproduced across multiple independent production requests.
Examples include:
/product-category/dob/dob-nocap//product-category/strip-led//product-category/full-spectrum-uv//shop/?filter_brand=xgd&_pjax=.wd-page-content/shop/?filter_brand=xgd&filter_color=...&_pjax=.wd-page-content/product/.../wp-admin/post.php?post=14&action=elementorThe request URLs vary, but the same WooCommerce shutdown callback remains the observed boundary.
Most importantly, the observed transition is repeatedly:
and not simply:
Relevant WooCommerce callback
The callback identified by the instrumentation is the anonymous shutdown closure created by:
at approximately line 121 of the installed WooCommerce 11.0.1 code.
The relevant stack path captured during the first observed 2014 query is:
The first observed 2014 query is:
The call stack points through:
and:
Exact observed query sequence
The latest diagnostic layer also records:
Immediately preceding successful
$wpdbquery:First observed 2014 query:
Connection state:
This pattern has now been observed repeatedly.
Representative diagnostic output
A representative capture looks like:
logger V10 — SHUTDOWN CULPRIT DETECTED
Culprit callback:
Closure @ .../woocommerce/src/Internal/BatchProcessing/BatchProcessingController.php:121
Priority on shutdown: 10
errno before callback: 0
errno after callback: 2014
more_results before callback: no
more_results after callback: no
field_count before callback: 1
field_count after callback: 1
thread_id: 66338
error:
Commands out of sync; you can't run this command now
last_query (wpdb):
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
observed 2014 query inside callback:
SELECT option_value
FROM wp_options
WHERE option_name = 'wc_pending_batch_processes'
LIMIT 1
immediate previous WPDB query:
SELECT a.action_id
FROM wp_actionscheduler_actions a
WHERE 1=1
AND a.hook='wc_schedule_pending_batch_processes'
AND a.status IN ('in-progress', 'pending')
LIMIT 0, 1
transition pattern:
ACTION_SCHEDULER_THEN_PENDING_OPTION
BatchProcessingController shutdown callback
->
Action Scheduler query
->
some internal DB/result/connection state transition
->
get_option('wc_pending_batch_processes')
->
errno=2014
callback starts
mysqli state before callback is recorded
callback executes normally
if a $wpdb query sees 2014, the observation remains pending
callback reaches its normal completion / finally boundary
mysqli state after callback is recorded
a callback is attributed only when:
mysqli_more_results() == yes
more_results before callback: no
more_results after callback: no
wp_actionscheduler_actions
wc_schedule_pending_batch_processes
wc_pending_batch_processes
Action Scheduler query
[errno=0]
->
BatchProcessingController shutdown callback
->
get_option('wc_pending_batch_processes')
[first observed errno=2014]
BatchProcessingController shutdown callback
errno before:
0
errno after:
2014
WordPress 7.1
WooCommerce 11.0.1
Action Scheduler 4.0.0
PHP 8.4.23
mysqlnd 8.4.23
MySQL 8.0.46
LiteSpeed
HPOS disabled
InnoDB tables
Persistent database connections disabled