MU Plugin: Query Dump with Backtraces
1. Create the MU plugin file
Path: {site-root}/wp-content/mu-plugins/query-dump.php
<?php
/**
* Query Dump — writes all $wpdb->queries to /tmp/wpdebug-queries.log on shutdown.
* Remove or rename this file when done.
*/
define( 'SAVEQUERIES', true );
add_action( 'shutdown', function () {
global $wpdb;
if ( empty( $wpdb->queries ) ) return;
$url = $_SERVER['REQUEST_URI'] ?? '(unknown)';
$time = date( 'Y-m-d H:i:s' );
$pid = getmypid();
$out = "\n\n=== REQUEST pid=$pid $time $url ===\n";
foreach ( $wpdb->queries as $i => $q ) {
// $q = [ sql, elapsed_ms, backtrace_string ]
$sql = trim( $q[0] );
$elapsed = round( $q[1] * 1000, 2 ); // ms
$trace = $q[2] ?? '';
$out .= "\n--- Query #" . ( $i + 1 ) . " ({$elapsed}ms) ---\n$sql\n$trace\n";
}
file_put_contents( '/tmp/wpdebug-queries.log', $out, FILE_APPEND | LOCK_EX );
} );
Why MU plugin: zero activation friction, survives plugin toggles, loads before everything else.
2. Verify it's loading
wp --path="$WP_PATH" eval 'echo defined("SAVEQUERIES") ? "ON" : "OFF";'
Should print ON. If it prints OFF, SAVEQUERIES was already defined earlier in wp-config.php — check there.
3. Clear the log and run your confirmed Playwright flow
rm -f /tmp/wpdebug-queries.log
node temp/pw-xdebug-profile-run.js # or whichever script drives the full prep flow
The flow hits: homepage → add-to-cart → coupon URL → homepage-after-prep. Each of those is a separate PHP process and will write a separate === REQUEST === block.
4. Read the log — find the right request block first
grep "=== REQUEST" /tmp/wpdebug-queries.log
You'll see one line per PHP request, with the URI. Identify which block is:
- the coupon URL request (
/?coupon-code=binoid15...)
- the final homepage request (after prep)
5. Search for the coupon lookup SQL specifically
grep -n "shop_coupon" /tmp/wpdebug-queries.log
This will show exactly which request block contains the binoid15 / shop_coupon query and its line number. Then read that block's backtrace:
# example: query showed up at line 84, read surrounding context
sed -n '80,120p' /tmp/wpdebug-queries.log
The backtrace string (field $q[2]) is a comma-separated call chain from $wpdb->query() up through WooCommerce/Smart Coupons. That chain will either show WC_SC_URL_Coupon, WC_SC_Coupon_Actions, or something else entirely.
6. Clean up
Once you have your answer, either delete the file or rename it so it stops running:
mv wp-content/mu-plugins/query-dump.php wp-content/mu-plugins/query-dump.php.off
What you're looking for
| Finding |
Implication |
shop_coupon lookup is in the coupon URL block |
The query fires during coupon application, not homepage rehydration |
shop_coupon lookup is in the homepage-after-prep block |
Cart/session rehydration on homepage is the source — primary theory confirmed |
Backtrace shows WC_SC_Coupon_Actions |
Coupon-actions path is the issuer — patch there |
Backtrace shows WC_SC_URL_Coupon |
URL/session rehydration path is the issuer |
Backtrace shows WC core (WC_Coupon->__construct) |
May be a WC core issue unrelated to Smart Coupons specifically |
MU Plugin: Query Dump with Backtraces
1. Create the MU plugin file
Path:
{site-root}/wp-content/mu-plugins/query-dump.phpWhy MU plugin: zero activation friction, survives plugin toggles, loads before everything else.
2. Verify it's loading
Should print
ON. If it printsOFF,SAVEQUERIESwas already defined earlier inwp-config.php— check there.3. Clear the log and run your confirmed Playwright flow
rm -f /tmp/wpdebug-queries.log node temp/pw-xdebug-profile-run.js # or whichever script drives the full prep flowThe flow hits: homepage → add-to-cart → coupon URL → homepage-after-prep. Each of those is a separate PHP process and will write a separate
=== REQUEST ===block.4. Read the log — find the right request block first
grep "=== REQUEST" /tmp/wpdebug-queries.logYou'll see one line per PHP request, with the URI. Identify which block is:
/?coupon-code=binoid15...)5. Search for the coupon lookup SQL specifically
grep -n "shop_coupon" /tmp/wpdebug-queries.logThis will show exactly which request block contains the
binoid15/shop_couponquery and its line number. Then read that block's backtrace:The backtrace string (field
$q[2]) is a comma-separated call chain from$wpdb->query()up through WooCommerce/Smart Coupons. That chain will either showWC_SC_URL_Coupon,WC_SC_Coupon_Actions, or something else entirely.6. Clean up
Once you have your answer, either delete the file or rename it so it stops running:
What you're looking for
shop_couponlookup is in the coupon URL blockshop_couponlookup is in the homepage-after-prep blockWC_SC_Coupon_ActionsWC_SC_URL_CouponWC_Coupon->__construct)