Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/cli/lib/wordpress-server-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,11 @@ export async function sendMessage(
processEventHandler = ( event ) => {
if ( event.process.name === processName && event.event === 'exit' ) {
exitRejectTimeoutId = setTimeout( () => {
reject( new Error( 'WordPress server process exited unexpectedly' ) );
let errorMessage = 'WordPress server process exited unexpectedly';
if ( event.stderrTail?.trim() ) {
errorMessage += `\n${ event.stderrTail.trimEnd() }`;
}
reject( new Error( errorMessage ) );
}, CHILD_EXIT_ERROR_GRACE_MS );
}
};
Expand Down
6 changes: 3 additions & 3 deletions apps/studio/src/hooks/use-site-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from 'react';
import { useContentTabs } from 'src/hooks/use-content-tabs';
import { useIpcListener } from 'src/hooks/use-ipc-listener';
import { simplifyErrorForDisplay } from 'src/lib/error-formatting';
import { simplifyErrorForDisplay, simplifyErrorToFirstSentence } from 'src/lib/error-formatting';
import { getIpcApi } from 'src/lib/get-ipc-api';
import type { Blueprint } from 'src/stores/wpcom-api';

Expand Down Expand Up @@ -271,7 +271,7 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
message = __(
'The selected Blueprint failed to execute properly. This could be due to invalid PHP code, missing plugins, or other issues in the Blueprint file. Please check your Blueprint file and try again.'
);
errorToShow = undefined;
errorToShow = simplifyErrorToFirstSentence( error );
} else {
title = __( 'Failed to create site' );
message = __(
Expand All @@ -285,7 +285,7 @@ export function SiteDetailsProvider( { children }: SiteDetailsProviderProps ) {
title,
message,
error: errorToShow,
showOpenLogs: ! isBlueprintError || ! hasBlueprint,
showOpenLogs: true,
} );

// Remove the temporary site immediately, but with a minor delay to ensure state updates properly
Expand Down
19 changes: 16 additions & 3 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ import {
trustRootCA,
} from 'src/lib/certificate-manager';
import { download } from 'src/lib/download';
import { simplifyErrorForDisplay } from 'src/lib/error-formatting';
import {
extractErrorFromProcessManagerLogs,
simplifyErrorForDisplay,
} from 'src/lib/error-formatting';
import { buildFeatureFlags } from 'src/lib/feature-flags';
import { getImageData } from 'src/lib/get-image-data';
import { getUserLocaleWithFallback } from 'src/lib/locale-node';
Expand Down Expand Up @@ -715,8 +718,10 @@ function readWordPressDebugLog( sitePath: string ): string[] | undefined {

function readProcessManagerLogs( siteId: string ): { stdout?: string[]; stderr?: string[] } {
const logsDir = nodePath.join( PROCESS_MANAGER_HOME, 'logs' );
const stdoutPath = nodePath.join( logsDir, `studio-site-${ siteId }-out.log` );
const stderrPath = nodePath.join( logsDir, `studio-site-${ siteId }-error.log` );
const dateTag = new Date().toISOString().slice( 0, 10 ).replace( /-/g, '' );
const prefix = `studio-site-${ siteId }`;
const stdoutPath = nodePath.join( logsDir, `${ prefix }-out-${ dateTag }.log` );
const stderrPath = nodePath.join( logsDir, `${ prefix }-error-${ dateTag }.log` );

return {
stdout: readLastLines( stdoutPath, DEBUG_LOG_MAX_LINES ),
Expand Down Expand Up @@ -873,6 +878,14 @@ export async function createSite(
contexts,
} );

// If the error message is generic, try to surface a more useful message from
// the process manager logs. The detailed error is often captured in stdout
// (e.g. blueprint execution errors logged by playground-cli).
const logErrorMessage = extractErrorFromProcessManagerLogs( processManagerLogs );
if ( logErrorMessage ) {
throw new Error( logErrorMessage );
}

throw error;
} finally {
if ( bundleTempDir ) {
Expand Down
34 changes: 34 additions & 0 deletions apps/studio/src/lib/error-formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,37 @@ export function simplifyErrorForDisplay( error: unknown ): Error {
}
return new Error( String( error ) );
}

/**
* Like `simplifyErrorForDisplay`, but also truncates to the first sentence.
* Useful when the first line is a long, detailed error message and the dialog
* should stay concise (e.g. blueprint errors). Full details are available via logs.
*/
export function simplifyErrorToFirstSentence( error: unknown ): Error {
const simplified = simplifyErrorForDisplay( error );
const firstSentence = simplified.message.match( /^[^.]+\./ );
return firstSentence ? new Error( firstSentence[ 0 ] ) : simplified;
}

/**
* Extract a user-facing error message from process manager logs. The child process often logs
* detailed error information to stdout (via playground-cli) before exiting. This scans log
* lines for "Error:" prefixed entries which typically contain the root cause.
*/
export function extractErrorFromProcessManagerLogs( logs: {
stdout?: string[];
stderr?: string[];
} ): string | undefined {
const lines = [ ...( logs.stdout ?? [] ), ...( logs.stderr ?? [] ) ];

for ( let i = lines.length - 1; i >= 0; i-- ) {
// Log lines are timestamped: "2026-05-15T08:38:04.859Z Error: ..."
// Strip the timestamp prefix to get the raw message.
const line = lines[ i ].replace( /^\d{4}-\d{2}-\d{2}T[\d:.]+Z\s*/, '' ).trim();
if ( line.startsWith( 'Error:' ) || line.startsWith( 'Error when' ) ) {
return line;
}
}

return undefined;
}