From 6fc5f342ddee0572b5affce378b766881e955908 Mon Sep 17 00:00:00 2001 From: LukeParkerDev <10430890+Hona@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:23:10 +1000 Subject: [PATCH] fix(desktop-wsl): time-bound every wsl.exe invocation to fail fast on wedge Ubuntu-24.04 in the failed first-run state wedges wsl.exe silently - no stdout, no exit. Without a timeout resolveWslOpencode (and any other runWsl call) blocks the sidecar spawn flow forever, which hides the real failure from logs and from the controller's failed runtime state. Add a 20s default ceiling to runCommand; caller can override for long-running jobs (installs). --- packages/desktop-electron/src/main/wsl.ts | 31 ++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/desktop-electron/src/main/wsl.ts b/packages/desktop-electron/src/main/wsl.ts index 7f441ceae9..3ad523b789 100644 --- a/packages/desktop-electron/src/main/wsl.ts +++ b/packages/desktop-electron/src/main/wsl.ts @@ -16,8 +16,19 @@ export type WslCommandResult = { type RunWslOptions = { onLine?: (line: WslCommandLine) => void signal?: AbortSignal + /** + * Ceiling on how long we wait for the child process to exit. When the + * LXSS service or a specific distro wedges (e.g. Ubuntu-24.04 with a + * pending first-run prompt), `wsl.exe` never returns and any command + * that doesn't specify a timeout hangs the entire startup flow. Default + * is 20s — enough for slow cold-starts, short enough to fail fast on + * a wedge. Callers can override for longer-running jobs. + */ + timeoutMs?: number } +const DEFAULT_WSL_TIMEOUT_MS = 20_000 + // `--user root` bypasses the distro's default-user requirement. A freshly // installed WSL distro (Ubuntu-24.04 in particular) prompts interactively // for a username/password on its first invocation; when spawned with @@ -51,6 +62,20 @@ function runCommand(command: string, args: string[], opts: RunWslOptions = {}) { signal: opts.signal, }) + // Guard every wsl.exe invocation with a timeout. When the distro or + // the LXSS service is wedged (Ubuntu first-run state, Windows update + // pending, etc.) wsl.exe produces no output and never exits; without + // this the whole sidecar spawn flow stalls the app forever. + const timeoutMs = opts.timeoutMs ?? DEFAULT_WSL_TIMEOUT_MS + const timeoutId = setTimeout(() => { + try { + child.kill() + } catch { + /* ignore */ + } + reject(new Error(`${command} ${args.join(" ")} timed out after ${timeoutMs}ms`)) + }, timeoutMs) + let stdout = "" let stderr = "" let stdoutPending = "" @@ -97,8 +122,12 @@ function runCommand(command: string, args: string[], opts: RunWslOptions = {}) { stderrPending = flush("stderr", stderrPending) }) - child.once("error", reject) + child.once("error", (error) => { + clearTimeout(timeoutId) + reject(error) + }) child.once("close", (code, signal) => { + clearTimeout(timeoutId) resolve({ code, signal, stdout, stderr }) }) })