diff --git a/packages/server/src/terminal/worker-terminal-manager.test.ts b/packages/server/src/terminal/worker-terminal-manager.test.ts index 2a584087a3..0bcc2acf92 100644 --- a/packages/server/src/terminal/worker-terminal-manager.test.ts +++ b/packages/server/src/terminal/worker-terminal-manager.test.ts @@ -70,7 +70,7 @@ import { TerminalWorkerIncompatibleError, } from "./terminal-worker-pool.js"; import { connectTerminalWorker } from "./terminal-worker-client.js"; -import { launchTerminalWorker } from "./terminal-worker-launch.js"; +import { launchTerminalWorker, TerminalWorkerLaunchError } from "./terminal-worker-launch.js"; type TerminalRow = TerminalState["grid"][number]; @@ -2671,3 +2671,34 @@ it.each([false, true])( }, 45000, ); + +// Both Windows reports in #8766: the worker's process identity query (PowerShell there, `ps` here) +// fails, the worker exits before writing its record, and the daemon used to exit with it. +it.skipIf(isPlatform("win32"))( + "starts without a worker when the worker cannot read its own identity, and launches one once it can", + async () => { + const paseoHome = createTemporaryHome(); + const binDir = mkdtempSync(join(tmpdir(), "worker-terminal-manager-no-ps-")); + temporaryDirs.push(binDir); + writeFileSync(join(binDir, "ps"), "#!/bin/sh\nexit 1\n", { mode: 0o755 }); + const originalPath = process.env.PATH; + process.env.PATH = `${binDir}${delimiter}${originalPath ?? ""}`; + try { + manager = await startRealWorkerManager({ paseoHome }); + expect(await workerRecords(paseoHome)).toEqual([]); + await expect( + manager.createTerminal({ workspaceId: "ws-no-identity", cwd: paseoHome }), + ).rejects.toThrow(TerminalWorkerLaunchError); + expect(await workerRecords(paseoHome)).toEqual([]); + } finally { + process.env.PATH = originalPath; + } + + const terminal = trackTerminal( + await manager.createTerminal({ workspaceId: "ws-no-identity", cwd: paseoHome }), + ); + expect(terminal.id).toBeTruthy(); + expect((await workerRecords(paseoHome)).map((record) => record.pid)).toHaveLength(1); + }, + 30000, +); diff --git a/packages/server/src/terminal/worker-terminal-manager.ts b/packages/server/src/terminal/worker-terminal-manager.ts index 9a82826cec..ea91d54a66 100644 --- a/packages/server/src/terminal/worker-terminal-manager.ts +++ b/packages/server/src/terminal/worker-terminal-manager.ts @@ -1269,8 +1269,17 @@ export async function startWorkerTerminalManager( try { current = await startCurrent(); } catch (error) { - if (!(error instanceof NoTerminalWorkerAvailableError)) throw error; - logger.warn(error.message); + if (error instanceof NoTerminalWorkerAvailableError) { + logger.warn(error.message); + } else { + // The daemon starts without terminals rather than not at all. A worker that cannot start + // here (its process identity query failed, say) is launched again by the first terminal + // creation, which reports the same error if it still cannot. + logger.error( + { err: error }, + "Terminal worker did not start; terminals are unavailable until one starts", + ); + } } }