Summary
When a blueprint combines three ingredients — login: true, a defineWpConfigConsts step, and any step that loads WordPress (runPHP that requires wp-load.php, activatePlugin, etc.) — @wp-playground/cli server crashes with a PHP fatal inside Playground's own injected mu-plugin /internal/shared/mu-plugins/0-playground.php:
PHP Fatal error: Uncaught Error: Undefined constant "SQLITE_MAIN_FILE"
in /internal/shared/mu-plugins/0-playground.php:11
Stack trace:
#0 /wordpress/wp-includes/class-wp-hook.php(341): {closure:/internal/shared/mu-plugins/0-playground.php:4}('')
#1 /wordpress/wp-includes/class-wp-hook.php(365): WP_Hook->apply_filters(NULL, Array)
#2 /wordpress/wp-includes/plugin.php(522): WP_Hook->do_action(Array)
#3 /wordpress/wp-settings.php(764): do_action('wp_loaded')
#4 /wordpress/wp-config.php(102): require_once('/wordpress/wp-s...')
#5 /wordpress/wp-load.php(50): require_once('/wordpress/wp-c...')
#6 /internal/eval.php(3): require_once('/wordpress/wp-l...')
Removing any one of the three ingredients makes the blueprint boot successfully.
Minimal reproduction
No mounts, no Jetpack, no custom files. Save as blueprint.json:
{
"$schema": "https://playground.wordpress.net/blueprint-schema.json",
"login": true,
"steps": [
{ "step": "defineWpConfigConsts", "consts": { "WP_DEBUG": false } },
{ "step": "runPHP", "code": "<?php require '/wordpress/wp-load.php'; echo 'ok';" }
]
}
Run:
npx --yes @wp-playground/cli@3.1.19 server --blueprint=./blueprint.json --port=19720
Expected: server starts. Actual: boot aborts with the fatal above (reported as "Error when executing the blueprint step #3").
Matrix of what fails and what works
All rows use runPHP requiring /wordpress/wp-load.php as the WP-loading step.
login: true |
defineWpConfigConsts |
WP-loading step |
Result |
| yes |
yes |
yes |
FAIL (fatal above) |
| yes |
yes |
no |
OK |
| yes |
no |
yes |
OK |
| no |
yes |
yes |
OK |
| no |
2× defineWpConfigConsts |
yes |
OK |
So the bug is specific to the login: true + defineWpConfigConsts + WP-load combination — not just "two blueprint steps that call defineConstant".
Versions affected
Reproduces on @wp-playground/cli 3.1.12, 3.1.15, 3.1.18, 3.1.19 — this is not a recent regression.
Environment: macOS 26.4 (arm64), Node v24.14.0. The CLI also logs Resolved WordPress release URL: https://downloads.w.org/release/wordpress-6.9.4.zip.
Probable root cause (speculative, not verified)
Playground's worker defines SQLITE_MAIN_FILE once during bootWordPress via defineConstant("SQLITE_MAIN_FILE", "1") (see @wp-playground/wordpress/index.js, the D(t, e) function that sets up the SQLite integration plugin).
However, the wrapper that creates new PHP runtimes (same file, function C / inner n) only re-applies a limited set of constants per runtime:
r.defineConstant("WP_SQLITE_AST_DRIVER", true);
if (t.constants) {
for (const c in t.constants) r.defineConstant(c, t.constants[c]);
}
SQLITE_MAIN_FILE is not in t.constants (those come from CLI args / blueprint-boot constants), so any secondary PHP runtime spawned after boot does not have SQLITE_MAIN_FILE defined. Meanwhile, Playground's own injected mu-plugin 0-playground.php references it unconditionally when DB_ENGINE === 'sqlite':
'driver_path' => defined('WP_MYSQL_ON_SQLITE_LOADER_PATH')
? WP_MYSQL_ON_SQLITE_LOADER_PATH
: dirname(SQLITE_MAIN_FILE) . '/wp-pdo-mysql-on-sqlite.php',
That would explain why the failure depends on having enough blueprint steps of the right kind to cause a secondary PHP runtime to handle the WP-loading step, but I haven't instrumented the worker to confirm it's actually a second runtime vs. something else wiping the constant on the primary runtime.
Workaround
Setting method: "rewrite-wp-config" on the defineWpConfigConsts step sidesteps the bug (it edits wp-config.php on disk instead of going through php.defineConstant()), but that shouldn't be necessary for the default method.
Suggested fixes
Either of:
- Define
SQLITE_MAIN_FILE (and any other boot-time SQLite constants) on every PHP runtime created by the per-runtime init path in @wp-playground/wordpress, not just on the one used during bootWordPress.
- Make
0-playground.php guard its reference: defined('SQLITE_MAIN_FILE') ? dirname(SQLITE_MAIN_FILE) . '/wp-pdo-mysql-on-sqlite.php' : null (or similar).
Option 1 addresses the root cause; option 2 hardens the mu-plugin but leaves other call sites exposed to the same runtime-propagation gap.
How I found this
Downstream report: https://github.com/Automattic/jetpack pnpm jetpack playground jetpack (a thin wrapper around npx @wp-playground/cli server) started failing for me. The Jetpack CLI's generated blueprint uses login: true, two defineWpConfigConsts steps, a writeFile, and activatePlugin, which is how I hit this. I trimmed it to the minimal blueprint above.
Summary
When a blueprint combines three ingredients —
login: true, adefineWpConfigConstsstep, and any step that loads WordPress (runPHPthat requireswp-load.php,activatePlugin, etc.) —@wp-playground/cli servercrashes with a PHP fatal inside Playground's own injected mu-plugin/internal/shared/mu-plugins/0-playground.php:Removing any one of the three ingredients makes the blueprint boot successfully.
Minimal reproduction
No mounts, no Jetpack, no custom files. Save as
blueprint.json:{ "$schema": "https://playground.wordpress.net/blueprint-schema.json", "login": true, "steps": [ { "step": "defineWpConfigConsts", "consts": { "WP_DEBUG": false } }, { "step": "runPHP", "code": "<?php require '/wordpress/wp-load.php'; echo 'ok';" } ] }Run:
Expected: server starts. Actual: boot aborts with the fatal above (reported as "Error when executing the blueprint step #3").
Matrix of what fails and what works
All rows use
runPHPrequiring/wordpress/wp-load.phpas the WP-loading step.login: truedefineWpConfigConstsdefineWpConfigConstsSo the bug is specific to the
login: true+defineWpConfigConsts+ WP-load combination — not just "two blueprint steps that calldefineConstant".Versions affected
Reproduces on
@wp-playground/cli3.1.12, 3.1.15, 3.1.18, 3.1.19 — this is not a recent regression.Environment: macOS 26.4 (arm64), Node v24.14.0. The CLI also logs
Resolved WordPress release URL: https://downloads.w.org/release/wordpress-6.9.4.zip.Probable root cause (speculative, not verified)
Playground's worker defines
SQLITE_MAIN_FILEonce duringbootWordPressviadefineConstant("SQLITE_MAIN_FILE", "1")(see@wp-playground/wordpress/index.js, theD(t, e)function that sets up the SQLite integration plugin).However, the wrapper that creates new PHP runtimes (same file, function
C/ innern) only re-applies a limited set of constants per runtime:SQLITE_MAIN_FILEis not int.constants(those come from CLI args / blueprint-boot constants), so any secondary PHP runtime spawned after boot does not haveSQLITE_MAIN_FILEdefined. Meanwhile, Playground's own injected mu-plugin0-playground.phpreferences it unconditionally whenDB_ENGINE === 'sqlite':That would explain why the failure depends on having enough blueprint steps of the right kind to cause a secondary PHP runtime to handle the WP-loading step, but I haven't instrumented the worker to confirm it's actually a second runtime vs. something else wiping the constant on the primary runtime.
Workaround
Setting
method: "rewrite-wp-config"on thedefineWpConfigConstsstep sidesteps the bug (it editswp-config.phpon disk instead of going throughphp.defineConstant()), but that shouldn't be necessary for the default method.Suggested fixes
Either of:
SQLITE_MAIN_FILE(and any other boot-time SQLite constants) on every PHP runtime created by the per-runtime init path in@wp-playground/wordpress, not just on the one used duringbootWordPress.0-playground.phpguard its reference:defined('SQLITE_MAIN_FILE') ? dirname(SQLITE_MAIN_FILE) . '/wp-pdo-mysql-on-sqlite.php' : null(or similar).Option 1 addresses the root cause; option 2 hardens the mu-plugin but leaves other call sites exposed to the same runtime-propagation gap.
How I found this
Downstream report: https://github.com/Automattic/jetpack
pnpm jetpack playground jetpack(a thin wrapper aroundnpx @wp-playground/cli server) started failing for me. The Jetpack CLI's generated blueprint useslogin: true, twodefineWpConfigConstssteps, awriteFile, andactivatePlugin, which is how I hit this. I trimmed it to the minimal blueprint above.