Fix browser terminal falsely reporting 'failed to start'
Deploy Ladill Hosting / deploy (push) Successful in 1m15s

createSession only waited 2.5s for the worker to flip starting->running, but
the worker cold-boots the framework (php artisan) + opens the shell, which under
load exceeds that. On timeout it also overwrote the (healthy) worker's metadata
to status=failed, so customers saw 'Terminal worker failed to start' even though
the shell was live. Wait up to 8s and return early once status is known; never
fabricate a failure — the worker records real boot errors itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isaacclad
2026-06-26 22:09:45 +00:00
co-authored by Claude Opus 4.8
parent 7e5ca3dce4
commit ff3959b734
@@ -10,6 +10,11 @@ class BrowserTerminalSessionManager
{ {
private const IDLE_TIMEOUT_SECONDS = 120; private const IDLE_TIMEOUT_SECONDS = 120;
// How long createSession() waits for the worker to cold-boot the framework
// and open the shell before returning. Generous so a slow/loaded boot isn't
// mistaken for a failure; the wait returns early once the status is known.
private const STARTUP_TIMEOUT_SECONDS = 8.0;
public function createSession(HostingAccount $account, int $userId, string $relativePath = '/public_html', int $cols = 120, int $rows = 30): array public function createSession(HostingAccount $account, int $userId, string $relativePath = '/public_html', int $cols = 120, int $rows = 30): array
{ {
$sessionId = (string) Str::uuid(); $sessionId = (string) Str::uuid();
@@ -334,7 +339,9 @@ class BrowserTerminalSessionManager
private function waitForWorkerStartup(string $sessionId, array $metadata): array private function waitForWorkerStartup(string $sessionId, array $metadata): array
{ {
$deadline = microtime(true) + 2.5; // The loop returns the moment status leaves "starting" (running OR a
// genuine error the worker recorded), so a fast boot still returns fast.
$deadline = microtime(true) + self::STARTUP_TIMEOUT_SECONDS;
while (microtime(true) < $deadline) { while (microtime(true) < $deadline) {
usleep(100000); usleep(100000);
@@ -346,28 +353,11 @@ class BrowserTerminalSessionManager
} }
} }
$metadata['status'] = 'failed'; // Still starting after the wait: do NOT fabricate a failure — that also
$metadata['exit_code'] = 1; // clobbered workers that were about to come up, which is exactly what made
$metadata['closed_at'] = now()->toIso8601String(); // healthy terminals report "failed to start". Return current metadata; the
$metadata['error'] = $this->workerBootstrapError($sessionId); // browser keeps polling and the worker writes a real error itself if boot
$this->writeMetadata($sessionId, $metadata); // ultimately fails.
return $this->readWorkerMetadata($sessionId);
return $metadata;
}
private function workerBootstrapError(string $sessionId): string
{
$output = trim((string) @file_get_contents($this->outputLogPath($sessionId)));
if ($output !== '') {
$lines = preg_split("/\r\n|\n|\r/", $output) ?: [];
$tail = trim((string) end($lines));
if ($tail !== '') {
return 'Terminal worker failed to start: '.$tail;
}
}
return 'Terminal worker failed to start.';
} }
} }