





















@@ -46,6 +46,31 @@ function isProcessAlive(pid: number): boolean {
4646}
4747}
484849+async function writeFakePromptCli(root: string, descendantPidPath: string): Promise<string> {
50+const fakeCli = path.join(root, "fake-prompt-cli.mjs");
51+const descendantScript = [
52+"process.on('SIGINT', () => {});",
53+"process.on('SIGTERM', () => {});",
54+"setInterval(() => {}, 1000);",
55+].join("");
56+await fs.writeFile(
57+fakeCli,
58+[
59+"#!/usr/bin/env node",
60+"import childProcess from 'node:child_process';",
61+"import fs from 'node:fs';",
62+"const descendant = childProcess.spawn(process.execPath, [",
63+" '--input-type=module',",
64+` '--eval', ${JSON.stringify(descendantScript)},`,
65+"], { stdio: 'ignore' });",
66+`fs.writeFileSync(${JSON.stringify(descendantPidPath)}, String(descendant.pid));`,
67+"setInterval(() => {}, 1000);",
68+].join("\n"),
69+{ mode: 0o755 },
70+);
71+return fakeCli;
72+}
73+4974async function waitForChildExit(
5075child: ReturnType<typeof spawn>,
5176timeoutMs = 8_000,
@@ -646,6 +671,94 @@ describe("script-specific dev tooling hardening", () => {
646671await fs.rm(keepRoot, { force: true, recursive: true });
647672});
648673674+it.runIf(process.platform !== "win32")(
675+"cleans Anthropic direct prompt descendants after timeout",
676+async () => {
677+const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-direct-prompt-tree-"));
678+tempDirs.push(tempRoot);
679+const descendantPidPath = path.join(tempRoot, "descendant.pid");
680+let descendantPid = 0;
681+const fakeClaudeBin = await writeFakePromptCli(tempRoot, descendantPidPath);
682+const probe = spawn(
683+process.execPath,
684+["--import", "tsx", "scripts/anthropic-prompt-probe.ts"],
685+{
686+cwd: process.cwd(),
687+env: {
688+ ...process.env,
689+CLAUDE_BIN: fakeClaudeBin,
690+OPENCLAW_PROMPT_TEXT: "timeout cleanup proof",
691+OPENCLAW_PROMPT_TIMEOUT_MS: "1000",
692+OPENCLAW_PROMPT_TRANSPORT: "direct",
693+},
694+stdio: "ignore",
695+},
696+);
697+698+try {
699+await waitForCondition(() => existsSync(descendantPidPath));
700+descendantPid = Number.parseInt(await fs.readFile(descendantPidPath, "utf8"), 10);
701+expect(Number.isInteger(descendantPid)).toBe(true);
702+expect(isProcessAlive(descendantPid)).toBe(true);
703+704+await expect(waitForChildExit(probe)).resolves.toEqual({ status: 0, signal: null });
705+await waitForCondition(() => !isProcessAlive(descendantPid));
706+} finally {
707+if (probe.pid && isProcessAlive(probe.pid)) {
708+process.kill(probe.pid, "SIGKILL");
709+}
710+if (descendantPid && isProcessAlive(descendantPid)) {
711+process.kill(descendantPid, "SIGKILL");
712+}
713+}
714+},
715+);
716+717+it.runIf(process.platform !== "win32")(
718+"cleans Anthropic direct prompt descendants on parent signal",
719+async () => {
720+const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-direct-parent-signal-"));
721+tempDirs.push(tempRoot);
722+const descendantPidPath = path.join(tempRoot, "descendant.pid");
723+let descendantPid = 0;
724+const fakeClaudeBin = await writeFakePromptCli(tempRoot, descendantPidPath);
725+const probe = spawn(
726+process.execPath,
727+["--import", "tsx", "scripts/anthropic-prompt-probe.ts"],
728+{
729+cwd: process.cwd(),
730+env: {
731+ ...process.env,
732+CLAUDE_BIN: fakeClaudeBin,
733+OPENCLAW_PROMPT_TEXT: "parent signal cleanup proof",
734+OPENCLAW_PROMPT_TIMEOUT_MS: "10000",
735+OPENCLAW_PROMPT_TRANSPORT: "direct",
736+},
737+stdio: "ignore",
738+},
739+);
740+741+try {
742+await waitForCondition(() => existsSync(descendantPidPath));
743+descendantPid = Number.parseInt(await fs.readFile(descendantPidPath, "utf8"), 10);
744+expect(Number.isInteger(descendantPid)).toBe(true);
745+expect(isProcessAlive(descendantPid)).toBe(true);
746+747+const probeExit = waitForChildExit(probe);
748+process.kill(probe.pid!, "SIGTERM");
749+await expect(probeExit).resolves.toEqual({ status: 143, signal: null });
750+await waitForCondition(() => !isProcessAlive(descendantPid));
751+} finally {
752+if (probe.pid && isProcessAlive(probe.pid)) {
753+process.kill(probe.pid, "SIGKILL");
754+}
755+if (descendantPid && isProcessAlive(descendantPid)) {
756+process.kill(descendantPid, "SIGKILL");
757+}
758+}
759+},
760+);
761+649762it("waits for the Anthropic prompt gateway child after SIGKILL cleanup", async () => {
650763const events = new EventEmitter();
651764const signals: NodeJS.Signals[] = [];
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。