




















@@ -21,6 +21,19 @@ function isProcessRunning(pid: number): boolean {
2121}
2222}
232324+async function waitForFile(pathToCheck: string, timeoutMs: number): Promise<void> {
25+const deadline = Date.now() + timeoutMs;
26+while (Date.now() < deadline) {
27+try {
28+await readFile(pathToCheck, "utf8");
29+return;
30+} catch {
31+await sleep(25);
32+}
33+}
34+throw new Error(`Timed out waiting for ${pathToCheck}`);
35+}
36+2437describe("Matrix QA CLI runtime", () => {
2538it("redacts secret CLI arguments in diagnostic command text", () => {
2639expect(
@@ -314,4 +327,55 @@ describe("Matrix QA CLI runtime", () => {
314327await rm(root, { force: true, recursive: true });
315328}
316329});
330+331+it("kills ignored-stdio descendants after a timed-out CLI exits gracefully", async () => {
332+if (process.platform === "win32") {
333+return;
334+}
335+const root = await mkdtemp(
336+path.join(resolvePreferredOpenClawTmpDir(), "matrix-qa-cli-timeout-ignored-stdio-"),
337+);
338+const childPidPath = path.join(root, "child.pid");
339+const grandchildPidPath = path.join(root, "grandchild.pid");
340+let childPid: number | undefined;
341+let grandchildPid: number | undefined;
342+try {
343+await mkdir(path.join(root, "dist"));
344+await writeFile(
345+path.join(root, "dist", "index.mjs"),
346+[
347+"import { spawn } from 'node:child_process';",
348+"import { writeFileSync } from 'node:fs';",
349+`writeFileSync(${JSON.stringify(childPidPath)}, String(process.pid));`,
350+"const grandchild = spawn(process.execPath, ['-e', 'process.on(\\'SIGTERM\\', () => {}); setInterval(() => {}, 1000);'], { stdio: 'ignore' });",
351+"grandchild.unref();",
352+`writeFileSync(${JSON.stringify(grandchildPidPath)}, String(grandchild.pid));`,
353+"process.on('SIGTERM', () => process.exit(0));",
354+"setInterval(() => {}, 1000);",
355+].join("\n"),
356+);
357+358+const run = runMatrixQaOpenClawCli({
359+args: ["matrix", "verify", "self"],
360+cwd: root,
361+env: process.env,
362+timeoutMs: 500,
363+});
364+await waitForFile(grandchildPidPath, 2_000);
365+366+await expect(run).rejects.toThrow(/timed out after 500ms/u);
367+368+childPid = Number(await readFile(childPidPath, "utf8"));
369+grandchildPid = Number(await readFile(grandchildPidPath, "utf8"));
370+expect(isProcessRunning(childPid)).toBe(false);
371+expect(isProcessRunning(grandchildPid)).toBe(false);
372+} finally {
373+for (const pid of [grandchildPid, childPid]) {
374+if (pid && isProcessRunning(pid)) {
375+process.kill(pid, "SIGKILL");
376+}
377+}
378+await rm(root, { force: true, recursive: true });
379+}
380+});
317381});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。