Skip to content

Fix site failing to start when it has a PHP error#2805

Open
gcsecsey wants to merge 6 commits intotrunkfrom
gcsecsey/fix-php-error-site-start
Open

Fix site failing to start when it has a PHP error#2805
gcsecsey wants to merge 6 commits intotrunkfrom
gcsecsey/fix-php-error-site-start

Conversation

@gcsecsey
Copy link
Contributor

@gcsecsey gcsecsey commented Mar 13, 2026

Related issues

How AI was used in this PR

AI assisted with drafting the implementation, exploring the Playground CLI internals, the feasibility of using mount-only mode, and reviewing the final code for reuse, quality, and efficiency issues.

Proposed Changes

When a site has a PHP fatal error (eg. this_function_does_not_exist() in functions.php), the server child process crashes with process.exit(1) and the user sees a generic "Failed to start site" error dialog. This happens because Playground's internal error handler calls process.exit(1) directly in a .catch(), bypassing all try-catch blocks. The user has no way to know what went wrong or fix it without digging through logs.

This PR catches PHP errors during startup and shows them in the browser instead:

  • Adds a runCLIWithoutExit() wrapper in wordpress-server-child.ts that overrides process.exit to throw instead of exiting, so PHP fatal errors become catchable exceptions. It also intercepts http.createServer to capture Playground's orphaned HTTP server, which is already bound to the port after a failed boot.
  • Repurposes the orphaned server as an error page server. When a PHP error is detected, its request handler is replaced with one that serves a styled error page showing the parsed error message. This avoids EADDRINUSE since the server is already holding the port.
  • Adds a watchForPhpChanges() file watcher that watches the site directory for .php file changes. When the user fixes the error, the watcher closes the orphaned server, attempts a full restart, and on success restores normal operation (blueprints applied, admin credentials set).
  • Extracts PHP error handling into a new apps/cli/lib/php-error-handling.ts module.
  • Captures console and stdout output during boot so the actual PHP error message can be parsed and displayed, rather than showing the generic "exit code 1" message.
  • Adds uncaughtException and unhandledRejection handlers to prevent stale async error callbacks from Playground's failed boot from crashing the child process.

Other approaches I explored

I also investigated using Playground's mount-only mode as an alternative approach. The idea was to retry with mount-only mode after a failed normal boot, skipping the WordPress boot (where the fatal error occurs) and starting the HTTP server with all site files mounted. Each browser request would then trigger fresh PHP execution, letting WordPress display errors naturally per-request, the same behavior you see when a fatal error occurs on an already-running server.

However, mount-only is a V2 runner concept only (worker-thread-v2.ts). The V1 worker that Studio currently uses silently ignores the mode parameter.

This approach would be worth revisiting once the V2 runner ships and mount-only becomes available.

Testing Instructions

  • Start Studio and create a new site
  • Edit the site's functions.php file and add an invalid function call like this_function_does_not_exist();
  • Start the site. Check that the site starts, and the site preview updates.
  • Open the site. Check that it displays a "PHP Error Detected" page in the browser with the specific error message.
  • Fix the error in functions.php
  • The site should automatically restart and be fully functional
  • Check for regressions by quitting and reopening Studio and letting the site start up
CleanShot.2026-03-17.at.12.47.59.mp4

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?

…ialog

When a site has a PHP fatal error (in functions.php, plugins, etc.), Studio now starts the site and displays the PHP error in the browser instead of showing an error dialog. This allows users to see exactly what's wrong.

The fix intercepts Playground CLI's process.exit(1) call and captures the actual PHP error output. The orphaned HTTP server is repurposed to serve an error page. A file watcher watches for .php changes and automatically restarts the server when the error is fixed.
@gcsecsey gcsecsey changed the title Fix: site starts with PHP errors, shows error in browser Fix site failing to start when it has a PHP error Mar 16, 2026
Move pure error-handling functions (isPhpUserError, parsePhpError,
generateErrorPageHtml, serveErrorPage) to apps/cli/lib/php-error-handling.ts
and simplify output capture by using the existing global console/stdout
interceptors instead of duplicating them inside runCLIWithoutExit.
When Playground fails before creating an HTTP server, the orphaned server
is null. Previously, the file watcher started unconditionally and the
function returned success — leaving nothing on the port and silently
retrying with no user feedback. Now the watcher only starts when there
is an orphaned server to serve the error page, and the PHP error is
sent as a console-message so the parent/UI can display it. Without an
orphaned server, the error is rethrown so the parent shows a dialog.
- Remove double-capture of stdout (console.log already captures to
  capturedBootOutput; the process.stdout.write interceptor was
  duplicating every entry)
- Clear lastCapturedOutput after successful server recovery to free
  stale error output from memory
- Clean up abortControllers record after message handling to prevent
  unbounded growth over the process lifetime
@gcsecsey gcsecsey requested a review from a team March 17, 2026 12:40
@gcsecsey gcsecsey marked this pull request as ready for review March 17, 2026 12:50
@fredrikekelund fredrikekelund self-requested a review March 17, 2026 13:01
The cleanup commit incorrectly removed the stdout capture line,
but Playground outputs PHP errors to stdout, not console.log.
Without this, parsePhpError falls back to the generic message.
@wpmobilebot
Copy link
Collaborator

wpmobilebot commented Mar 17, 2026

📊 Performance Test Results

Comparing 4c7db85 vs trunk

app-size

Metric trunk 4c7db85 Diff Change
App Size (Mac) 1236.36 MB 1236.37 MB +0.02 MB ⚪ 0.0%

site-editor

Metric trunk 4c7db85 Diff Change
load 1899 ms 1907 ms +8 ms ⚪ 0.0%

site-startup

Metric trunk 4c7db85 Diff Change
siteCreation 6101 ms 6103 ms +2 ms ⚪ 0.0%
siteStartup 3924 ms 3914 ms 10 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants