From 2634a343dd585fdc994260658977d7716aaecc0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Tue, 27 Oct 2026 11:09:05 +0000 Subject: [PATCH] fix(engine): a Chrome shell that ignores SIGTERM can no longer freeze the CLI at launch (#6547) The start probe waited for the shell to exit after SIGTERM, so a shell that stalled under load blocked the whole process. Kill it outright at the timeout. --- .../src/services/chromeHostCeiling.test.ts | 21 ++++++++++++++++++- .../engine/src/services/chromeHostCeiling.ts | 6 ++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/engine/src/services/chromeHostCeiling.test.ts b/packages/engine/src/services/chromeHostCeiling.test.ts index 76d16d3c17..cc8e9e6bf0 100644 --- a/packages/engine/src/services/chromeHostCeiling.test.ts +++ b/packages/engine/src/services/chromeHostCeiling.test.ts @@ -1,5 +1,8 @@ +import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { chromeMajorCeiling, exceedsChromeCeiling } from "./chromeHostCeiling.js"; +import { chromeMajorCeiling, exceedsChromeCeiling, shellStarts } from "./chromeHostCeiling.js"; describe("chromeMajorCeiling", () => { it.each([ @@ -22,3 +25,19 @@ describe("exceedsChromeCeiling", () => { expect(exceedsChromeCeiling(name, ceiling)).toBe(expected); }); }); + +describe.skipIf(process.platform === "win32")("shellStarts", () => { + it("returns at its timeout even when the shell ignores SIGTERM", () => { + const dir = mkdtempSync(join(tmpdir(), "hf-shell-probe-")); + const binary = join(dir, "shell.sh"); + writeFileSync(binary, "#!/bin/sh\ntrap '' TERM\nsleep 6\n"); + chmodSync(binary, 0o755); + const started = Date.now(); + try { + expect(shellStarts(binary, 300)).toBe(false); + expect(Date.now() - started).toBeLessThan(3000); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/packages/engine/src/services/chromeHostCeiling.ts b/packages/engine/src/services/chromeHostCeiling.ts index b49308d1ac..a2dbe4f0ed 100644 --- a/packages/engine/src/services/chromeHostCeiling.ts +++ b/packages/engine/src/services/chromeHostCeiling.ts @@ -64,11 +64,13 @@ const starts = new Map(); const debug = debuglog("hyperframes:browser"); /** Whether a shell loads a blank page and exits: an old build can die at launch on a newer OS. Asked once per path. */ -export function shellStarts(binary: string): boolean { +export function shellStarts(binary: string, timeoutMs = 10_000): boolean { let answer = starts.get(binary); if (answer === undefined) { const probe = ["--no-sandbox", "--dump-dom", "about:blank"]; - answer = spawnSync(binary, probe, { stdio: "ignore", timeout: 10_000 })?.status === 0; + answer = + spawnSync(binary, probe, { stdio: "ignore", timeout: timeoutMs, killSignal: "SIGKILL" }) + ?.status === 0; if (!answer) debug("skipping %s: it did not start", binary); starts.set(binary, answer); }