Most Moodle Playground problems fall into one of three categories: PHP/bootstrap errors that are hidden by default, SQLite driver incompatibilities with a Moodle version, or networking constraints from running inside a browser sandbox. This guide covers the most common symptoms and how to resolve them.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ateeducacion/moodle-playground/llms.txt
Use this file to discover all available pages before exploring further.
Quick checks
Enable debug mode
Add?debug=true to any Moodle Playground URL to force Moodle developer debug mode and enable display_errors for the booted session:
true, developer, normal, minimal, 0.
Syntax check key files
Run these before suspecting a runtime bug — a syntax error in any of these files causes silent failures:First place to look
If the browser is failing during install or first load, inspect these in order:- Browser console — JavaScript errors and stack traces
- Network requests for
/playground/main/php83-cgi/…— check for 404s, failed fetches, or unexpected redirects - Shell progress log — shows the last bootstrap stage reached
- PHP Info panel in the shell toolbar — confirms which PHP version and extensions are loaded
- Rerun with
?debug=trueif the page is hiding PHP/Moodle errors
src/runtime/bootstrap.jssrc/remote/main.jssw.jssrc/runtime/php-compat.jslib/moodle-loader.js
Symptom reference
TypeError: resolved is not a function
TypeError: resolved is not a function
- Malformed or unreadable
install.xml - A regression in the XML/runtime path outside the current common patch set
install.xml file first and re-run the XMLDB load checks against the current unpatched Moodle sources.Fatal in sqlite_sql_generator
Fatal in sqlite_sql_generator
- Signature mismatch with
sql_generator - Missing
getCreateTempTableSQL
patches/shared/lib/ddl/sqlite_sql_generator.phpNote: This is a Moodle-core compatibility problem, not a WASM routing problem. The fix must be applied both to the upstream fork and to the local patch copy in this repo (see SQLite driver bugs below).Fatal in sqlite3_pdo_moodle_database
Fatal in sqlite3_pdo_moodle_database
is_temptable() on null- Driver returns
falsewhen Moodle 5 expects[] - Deprecated
reset()on object
patches/shared/lib/dml/sqlite3_pdo_moodle_database.php- Runtime override in
src/runtime/bootstrap.js
Fatal in admin/plugins.php: Failed opening required …/analytics/tests/classes/mlbackend_helper_trait.php
Fatal in admin/plugins.php: Failed opening required …/analytics/tests/classes/mlbackend_helper_trait.php
*/tests/* directory to halve the download size. Some Moodle 5.0+ production code require_once()s a file that lives under tests/. Specifically, lib/mlbackend/python/classes/processor.php requires the analytics test trait core_analytics\tests\mlbackend_helper_trait. Building the admin tree instantiates every mlbackend plugin, so loading /admin/plugins.php or any analytics admin page fatals on the excluded file.Files:scripts/patch-moodle-source.sh— drops the test-traitrequire_once/useand inlines the one method the class actually usesdocs/decisions/0014-production-require-of-tests-files-patch.md
make up-local does not reproduce this — it serves the full checkout with tests/ present. Reproduce and verify only against a real bundle (make bundle). If a different Moodle branch fatals on another …/tests/… require, generalize the fix into a build-time detector (see ADR 0014 Review Criteria).Invalid cache store in config warnings everywhere
Invalid cache store in config warnings everywhere
src/runtime/config-template.jslib/config-template.js- Runtime override in
src/runtime/bootstrap.js
CACHE_DISABLE_ALL is active. If they become fatal again, inspect cache/classes/config.php and cache/classes/cache.php.Undefined constant "core\\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES"
Undefined constant "core\\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES"
sodium extension. Some Moodle paths assume sodium-first encryption unless the local fallback patch is active.Files:patches/shared/lib/classes/encryption.php- Runtime override in
src/runtime/bootstrap.js src/runtime/config-template.jslib/config-template.js
rememberusernameis disabled by defaultcore\encryptionfalls back to OpenSSL in this prototypeadmin/environment.xmlis downgraded at runtime so upgrades are not blocked by the missing extension
Undefined array key "HTTP_USER_AGENT"
Undefined array key "HTTP_USER_AGENT"
HTTP_* server variables.File: src/runtime/php-compat.jsNo input file specified (styles_debug.php, javascript.php, yui_combo.php)
No input file specified (styles_debug.php, javascript.php, yui_combo.php)
sw.jssrc/runtime/php-compat.jssrc/runtime/php-loader.js
- The request URL as seen by the Service Worker
- The forwarded request URL sent to the PHP worker
- The computed
SCRIPT_NAMEandSCRIPT_FILENAMEin the PHP request handler
ERR_TOO_MANY_REDIRECTS or "Incorrect access detected"
ERR_TOO_MANY_REDIRECTS or "Incorrect access detected"
$CFG->wwwrootbuilt from the wrong base URL- Scoped redirects losing query parameters
src/runtime/bootstrap.jsphp-worker.jssw.jssrc/shared/storage.js
Timed out while waiting for php-worker readiness
Timed out while waiting for php-worker readiness
- A bootstrap JavaScript error before
worker-readywas emitted - A stale service worker or stale scope
- Bundle load failure
php-worker.jssrc/remote/main.jssrc/runtime/bootstrap.js
- Hard-reload the page (Ctrl+Shift+R / Cmd+Shift+R)
- Click Reset in the shell toolbar
- Check the shell log for the last bootstrap step reached
- Retry with
?debug=trueto surface hidden PHP/bootstrap errors
Outbound HTTPS from PHP behaves differently depending on the target
Outbound HTTPS from PHP behaves differently depending on the target
tcpOverFetch is active, but not every HTTPS destination behaves identically. The browser-side fetch performed by the runtime does not trust self-signed local certificates, so local HTTPS servers fail before fallback.What is supported:- Direct PHP requests to trusted external HTTPS origins, including CORS-open URLs like
raw.githubusercontent.com - Direct PHP requests to the tested eXeLearning GitHub feed and release ZIP URLs
phpCorsProxyUrlas the browser-side fallback for PHP networkingMOODLE_PLAYGROUND_PROXY_URLas an explicit same-origin PHP networking endpoint when a plugin prefers a stable playground-only proxy contract
- Direct HTTPS to a self-signed local HTTPS server — the browser does not trust that certificate
src/runtime/php-loader.jssw.jssrc/runtime/config-template.jstests/e2e/php-networking.spec.mjs
Site is still in English after setting the language (or langimport fails with CORS)
Site is still in English after setting the language (or langimport fails with CORS)
dataroot/lang/<code>. Setting lang/locale alone only changes the preference, it does not install the pack.If admin/tool/langimport fails with a CORS error on download.moodle.org/langpack/<v>/languages.md5, the proxy is not authorizing the langpack index.What works:- The github-proxy allowlists
/langpack/paths (includinglanguages.md5) ondownload.moodle.organdpackaging.moodle.org, so the native Site administration → Language → Language packs UI installs directly. - Language packs are GET requests, so they work in Chromium, Firefox, and Safari — unlike streaming POST uploads which hit
duplex: 'half'limits on Firefox/Safari. - In blueprints, use the
installLanguagePackstep, or setoptions.localein theinstallMoodlestep — the pack auto-installs on boot viarunLanguageAutoInstall()inbootstrap.js.
isMoodleLangpackUrl in scripts/github-proxy-worker.js), that it is reachable, and that phpCorsProxyUrl is set in playground.config.json.Files:scripts/github-proxy-worker.jssrc/blueprint/steps/moodle-language.jssrc/runtime/bootstrap.js
restoreCourse fails or the course is missing after restore
restoreCourse fails or the course is missing after restore
- The
.mbzis large or complex. Restore runs entirely in the WASM runtime, which has limited memory and a SQLite driver that crashes on nested savepoints (ADR-0003). Large backups (many activities, large files, completion data) can exceed memory or hit transaction limits. - The
urlis not reachable: the host must be CORS-accessible (for exampleraw.githubusercontent.com) or proxy-allowlisted, otherwise the in-PHP download fails.
- A failed restore is reported in the boot log and is non-fatal — the rest of the blueprint still runs. The course simply will not be created.
- Prefer smaller course backups.
- Host large
.mbzfiles at a CORS-accessible URL so they stream into the runtime (theurlsource) instead of being buffered in memory (thedatasource). - If it fails only in one browser, check the URL’s CORS headers — the download is a GET via
tcpOverFetch, which works across Chromium, Firefox, and Safari.
src/blueprint/steps/moodle-restore.jssrc/blueprint/php/helpers.js
PHP worker bridge timed out
PHP worker bridge timed out
- The worker is alive but the request handler is blocked
- Often follows a fatal in PHP or a very slow or stuck bootstrap
php-worker.jssrc/remote/main.jssrc/runtime/bootstrap.js
?debug=true to surface any hidden PHP errors.White iframe, but URL and title inside Moodle are correct — resolved
White iframe, but URL and title inside Moodle are correct — resolved
src/remote/main.js is still present as a safety net.If this symptom reappears after changes to routing or bootstrap, inspect:src/remote/main.js—isFrameDocumentStalled(),scheduleFrameRecovery()sw.js— HTML response rewritingsrc/runtime/php-compat.js—$_SERVERvariable construction
RangeError: Array buffer allocation failed — resolved
RangeError: Array buffer allocation failed — resolved
content-length is known.If this reappears after changes to the loader, inspect lib/moodle-loader.js.Warnings like "Undefined property: stdClass::$frontpage"
Warnings like "Undefined property: stdClass::$frontpage"
config.php or the persisted config table.Files:src/runtime/config-template.jslib/config-template.jssrc/runtime/bootstrap.js
navcourselimit, enablecompletion, frontpage, frontpageloggedin, frontpagecourselimit, guestloginbutton, rememberusername, auth_instructions, maintenance_enabled, maxbytes.SQLite driver bugs
If the error originates from the DML or DDL layer (sqlite3_pdo_moodle_database.php or sqlite_sql_generator.php), it is a driver bug that must be fixed in two places:
- The upstream fork ateeducacion/moodle (three maintained branches for Moodle tracker PRs)
- The local patch copies in this repository under
patches/shared/lib/dml/andpatches/shared/lib/ddl/
Fix workflow
Reproduce and fix on the workbench branch
mdl-88218-workbench branch with make up (local PHP + SQLite) to reproduce the issue and verify the fix.Replicate to all upstream branches
ateeducacion/moodle fork.Apply the fix to local patch copies
patches/shared/lib/dml/ or patches/shared/lib/ddl/ in this repository.Trigger a rebuild
ddltablenotexist: Table "backup_ids_temp" does not exist
Root cause: fetch_columns() only queried sqlite_master but SQLite stores temporary tables in sqlite_temp_master. Fixed by adding a UNION ALL with sqlite_temp_master (issue #48).
If install fails mid-way
Inspect the staged bootstrap messages emitted bysrc/runtime/bootstrap.js in the shell log:
| Stage of failure | Where to look |
|---|---|
Before core:schema-load:done | Inspect XMLDB patches |
| During schema SQL execution | Inspect SQLite DDL generator and DML driver |
During finalize | Inspect config hydration and brittle plugin settings files |
| After install, on first navigation | Inspect wwwroot, redirects, asset URLs, CGI env, and iframe recovery |
PHP extensions available in the WASM runtime
The@php-wasm/web PHP runtime (versions 8.1–8.5, default 8.3) includes these extensions built into the WASM binary:
sodium is not available. The repository relies on the OpenSSL fallback patches in patches/shared/lib/classes/encryption.php.curl is available as an extension but actual network requests from WASM use a fetch-based transport, not real sockets. When playground.config.json defines phpCorsProxyUrl, the runtime uses that proxy as the browser-side fallback for outbound PHP HTTP(S) traffic. This is separate from addonProxyUrl, which handles browser-side ZIP and plugin downloads via the Service Worker proxy endpoint.