Skip to content

Repeated MySQL 2014 during WooCommerce shutdown: Action Scheduler query followed by wc_pending_batch_processes failure #1372

Description

@mmdrzw122

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:

errno=2014
    ->
errno=2014

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:

  1. callback starts

  2. mysqli state before callback is recorded

  3. callback executes normally

  4. if a $wpdb query sees 2014, the observation remains pending

  5. callback reaches its normal completion / finally boundary

  6. mysqli state after callback is recorded

  7. 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


For example:

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


for:

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]


and independently:

BatchProcessingController shutdown callback

errno before:
0

errno after:
2014


The environment is:

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions