Skip to content

CLI: Fatal "Undefined constant SQLITE_MAIN_FILE" when combining login: true + defineWpConfigConsts + a WordPress-loading step #3489

@JanJakes

Description

@JanJakes

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

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions