From 48b338a5a9b1fee734aeb96b6a85ce0a3dd6801a Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 20 Jun 2026 16:14:16 +0200 Subject: [PATCH] fix(e2e): report signaled host server startups --- scripts/e2e/parallels/host-server.ts | 8 +++-- test/scripts/parallels-smoke-model.test.ts | 36 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/scripts/e2e/parallels/host-server.ts b/scripts/e2e/parallels/host-server.ts index 42be85fd1eb5..18ee2c3c01c6 100644 --- a/scripts/e2e/parallels/host-server.ts +++ b/scripts/e2e/parallels/host-server.ts @@ -151,11 +151,11 @@ async function waitForHostServer( }); const startedAt = Date.now(); while (Date.now() - startedAt < 10_000) { - if (child.exitCode != null) { + if (hasHostServerChildExited(child)) { if (!childClosed) { await Promise.race([childClose, delay(HOST_SERVER_STDERR_DRAIN_MS)]); } - die(`host artifact server exited early: ${stderr.trim() || `exit ${child.exitCode}`}`); + die(`host artifact server exited early: ${stderr.trim() || formatHostServerExit(child)}`); } if (await canConnect(port)) { return; @@ -176,6 +176,10 @@ function appendBoundedOutput(previous: string, chunk: Buffer, limitBytes: number return combined.subarray(combined.byteLength - limitBytes).toString("utf8"); } +function formatHostServerExit(child: ChildProcessWithoutNullStreams): string { + return child.signalCode ? `signal ${child.signalCode}` : `exit ${child.exitCode ?? "unknown"}`; +} + async function canConnect(port: number): Promise { return await new Promise((resolve) => { const socket = createConnection({ host: "127.0.0.1", port }); diff --git a/test/scripts/parallels-smoke-model.test.ts b/test/scripts/parallels-smoke-model.test.ts index 93375cdca681..742d682a2988 100644 --- a/test/scripts/parallels-smoke-model.test.ts +++ b/test/scripts/parallels-smoke-model.test.ts @@ -557,6 +557,42 @@ exit 42 }, ); + it.runIf(process.platform !== "win32")( + "reports signaled host artifact server startup exits immediately", + async () => { + const tempDir = mkdtempSync(join(tmpdir(), "openclaw-parallels-host-server-signal-")); + const fakePython = join(tempDir, "python3"); + writeFileSync( + fakePython, + `#!/usr/bin/env bash +kill -TERM "$$" +`, + ); + chmodSync(fakePython, 0o755); + + try { + const port = await unusedLoopbackPort(); + const result = spawnNodeEvalSync( + `import { startHostServer } from "./${TS_PATHS.hostServer}"; await startHostServer({ dir: ".", hostIp: "127.0.0.1", port: ${port}, artifactPath: "artifact.tgz", label: "artifact" });`, + { + env: { + ...process.env, + PATH: `${tempDir}${delimiter}${process.env.PATH ?? ""}`, + }, + imports: ["tsx"], + maxBuffer: 1024 * 1024, + }, + ); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("host artifact server exited early: signal SIGTERM"); + expect(result.stderr).not.toContain("did not start"); + } finally { + rmSync(tempDir, { force: true, recursive: true }); + } + }, + ); + it("quotes shell args and resolves fuzzy snapshot hints through the shared TypeScript helper", () => { const tempDir = mkdtempSync(join(tmpdir(), "openclaw-parallels-helper-")); writeFakePrlctl(