You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Query Guard's observation subsystem produces zero telemetry for queries killed by WP Engine's platform query governor — which are, by definition, the worst queries on the site. On bloomzhemp production, WPE's governor killed 7 WooCommerce order queries every night for six consecutive nights (42 events verified in the debug logs), while Query Guard — active in the same process, in enforce mode, with the v2 db.php drop-in installed — logged zeroslow_query events and zeroquery_killed events for them across the entire period.
Flagged in Matthew's Jul 20 bloomzhemp log review (context: BinoidCBD/universal-child-theme-oct-2024#925). Root cause verified by Claude Code against the plugin source, WP core, and WP Engine's mu-plugin on production.
Root cause — the kill happens in PHP, before MySQL
WP Engine's governor (mu-plugins/wpengine-common/plugin.php, check_and_govern(), registered on the query filter at default priority) blocks oversized SELECTs by text length, not by runtime:
QUERY_LENGTH_MAX = 16384 — a SELECT longer than 16 KB is logged as KILLED QUERY (N characters long generated in file:line): … and the filter returns ''.
The nightly Awin order queries are 22–26 KB of WHERE ID IN (…) SQL, so they are blanked every time.
The empty string then hits WP core's early return in wpdb::query():
The query is never sent to MySQL, no error is raised, and $wpdb->last_error is never set. Every one of Query Guard's detection paths is downstream of an execution that never happens:
Detection path
Why it stays silent
slow_query via v2 drop-in (db.php:143-158)
parent::query() returns false in ~0 ms; $elapsed never reaches the 5 s WARN_THRESHOLD_MS, so the query is never appended to hcqg_slow_queries.
query_killed via capture_pending_kill() (hypercart-query-guard.php:1105-1124)
Bails at the empty( $wpdb->last_error ) check — wpdb's early return sets no error. The two signature matches (maximum statement execution time, query execution was interrupted) are never even consulted.
Enforce-mode MAX_EXECUTION_TIME
Session timeout only applies to queries MySQL executes; these never arrive.
Verified empirically: six days of production debug logs contain 42 KILLED QUERY lines and zero occurrences of any MySQL kill signature (query execution was interrupted, maximum statement execution time, lost connection, server has gone away). The kills are purely PHP-side.
Why this matters
The observation subsystem's implicit promise is "the slow-query log shows you your worst queries." Under a host governor, the very worst queries — bad enough that the host refuses to run them — are the only ones guaranteed to be invisible. An operator tuning from slow_query events alone would conclude these code paths are healthy.
The concrete consequence on bloomzhemp: the Awin approve_orders_daily() cron has its order queries blanked nightly, then fatals (get_id() on int) because WC_Order_Factory receives false instead of rows. Query Guard's logs offered no lead; root-causing required WPE's own log lines.
Detect governor blanking with a filter sandwich. Query Guard already registers on the query filter at priority 1 (hypercart-query-guard.php:249). Add a second capture at a very late priority (e.g. PHP_INT_MAX): the priority-1 hook stashes the incoming SQL in a static; the late hook fires after WPE's governor (priority 10 and 999) and, if the query arrives empty while the stashed original was non-empty, logs a new query_blocked_by_host event carrying the original SQL (truncated), length, context, and caller. This requires no knowledge of the host's implementation — any filter that blanks a query is caught.
Log failed queries in the drop-in regardless of duration.HCQG_DB::query() currently records only elapsed >= threshold. Recording parent::query() === false results (with last_error, length, elapsed) would surface both this case and fast hard failures generally, cheaply.
Broaden the query_killed signature list to cover connection-level kills (Lost connection to MySQL server during query, MySQL server has gone away), which also bypass the current two-pattern match.
Leading indicator (optional):classify_sql() already estimates IN-list sizes. A warn-level event for SELECTs approaching the 16 KB governor limit (e.g. > 8 KB) would flag queries before the host starts silently blanking them.
Environment
bloomzhemp production (WP Engine), WP 7.0.2, plugin v1.2.0 as mu-plugin, v2 db.php drop-in active
Log evidence: nightly KILLED QUERY ×7 at 00:00:10 UTC in debug.log.1–5.gz, Jul 14–20; hypercart-2026-07-{17..20}.log contain zero slow_query / query_killed events
Summary
Query Guard's observation subsystem produces zero telemetry for queries killed by WP Engine's platform query governor — which are, by definition, the worst queries on the site. On bloomzhemp production, WPE's governor killed 7 WooCommerce order queries every night for six consecutive nights (42 events verified in the debug logs), while Query Guard — active in the same process, in enforce mode, with the v2
db.phpdrop-in installed — logged zeroslow_queryevents and zeroquery_killedevents for them across the entire period.Flagged in Matthew's Jul 20 bloomzhemp log review (context: BinoidCBD/universal-child-theme-oct-2024#925). Root cause verified by Claude Code against the plugin source, WP core, and WP Engine's mu-plugin on production.
Root cause — the kill happens in PHP, before MySQL
WP Engine's governor (
mu-plugins/wpengine-common/plugin.php,check_and_govern(), registered on thequeryfilter at default priority) blocks oversized SELECTs by text length, not by runtime:QUERY_LENGTH_MAX = 16384— a SELECT longer than 16 KB is logged asKILLED QUERY (N characters long generated in file:line): …and the filter returns''.WHERE ID IN (…)SQL, so they are blanked every time.The empty string then hits WP core's early return in
wpdb::query():The query is never sent to MySQL, no error is raised, and
$wpdb->last_erroris never set. Every one of Query Guard's detection paths is downstream of an execution that never happens:slow_queryvia v2 drop-in (db.php:143-158)parent::query()returnsfalsein ~0 ms;$elapsednever reaches the 5 sWARN_THRESHOLD_MS, so the query is never appended tohcqg_slow_queries.query_killedviacapture_pending_kill()(hypercart-query-guard.php:1105-1124)empty( $wpdb->last_error )check — wpdb's early return sets no error. The two signature matches (maximum statement execution time,query execution was interrupted) are never even consulted.MAX_EXECUTION_TIMEVerified empirically: six days of production debug logs contain 42
KILLED QUERYlines and zero occurrences of any MySQL kill signature (query execution was interrupted,maximum statement execution time,lost connection,server has gone away). The kills are purely PHP-side.Why this matters
slow_queryevents alone would conclude these code paths are healthy.approve_orders_daily()cron has its order queries blanked nightly, then fatals (get_id() on int) becauseWC_Order_Factoryreceivesfalseinstead of rows. Query Guard's logs offered no lead; root-causing required WPE's own log lines.Proposed fixes
queryfilter at priority 1 (hypercart-query-guard.php:249). Add a second capture at a very late priority (e.g.PHP_INT_MAX): the priority-1 hook stashes the incoming SQL in a static; the late hook fires after WPE's governor (priority 10 and 999) and, if the query arrives empty while the stashed original was non-empty, logs a newquery_blocked_by_hostevent carrying the original SQL (truncated), length, context, and caller. This requires no knowledge of the host's implementation — any filter that blanks a query is caught.HCQG_DB::query()currently records onlyelapsed >= threshold. Recordingparent::query() === falseresults (withlast_error, length, elapsed) would surface both this case and fast hard failures generally, cheaply.query_killedsignature list to cover connection-level kills (Lost connection to MySQL server during query,MySQL server has gone away), which also bypass the current two-pattern match.classify_sql()already estimates IN-list sizes. A warn-level event for SELECTs approaching the 16 KB governor limit (e.g. > 8 KB) would flag queries before the host starts silently blanking them.Environment
db.phpdrop-in activeHYPERCART_QUERY_GUARD_MODE = enforcethroughout the observation window (throttle mode wasenforcethrough Jul 20, moved toobserveper Queue-depth-only escalation throttles an idle database: Jul 20 flood ran 2h23m with threads_running ≤ 5 #46)KILLED QUERY×7 at 00:00:10 UTC indebug.log.1–5.gz, Jul 14–20;hypercart-2026-07-{17..20}.logcontain zeroslow_query/query_killedeventsRelated