diff --git a/scripts/e2e/cron-mcp-cleanup-docker-client.ts b/scripts/e2e/cron-mcp-cleanup-docker-client.ts index 9d0599d9db0c..08cdc5441ddc 100644 --- a/scripts/e2e/cron-mcp-cleanup-docker-client.ts +++ b/scripts/e2e/cron-mcp-cleanup-docker-client.ts @@ -35,11 +35,18 @@ export function assertCronFinishedOk(finished: CronFinishedPayload | undefined): } } +function parseProbePid(raw: string): number | undefined { + const text = raw.trim(); + if (!/^[1-9]\d*$/u.test(text)) { + return undefined; + } + const pid = Number(text); + return Number.isSafeInteger(pid) ? pid : undefined; +} + async function readProbePid(pidPath: string): Promise { try { - const raw = (await fs.readFile(pidPath, "utf-8")).trim(); - const pid = Number.parseInt(raw, 10); - return Number.isInteger(pid) && pid > 0 ? pid : undefined; + return parseProbePid(await fs.readFile(pidPath, "utf-8")); } catch { return undefined; } @@ -51,8 +58,8 @@ async function readProbePids(pidsPath: string): Promise { const pids: number[] = []; const seen = new Set(); for (const line of raw.split(/\r?\n/)) { - const pid = Number.parseInt(line.trim(), 10); - if (!Number.isInteger(pid) || pid <= 0 || seen.has(pid)) { + const pid = parseProbePid(line); + if (pid === undefined || seen.has(pid)) { continue; } seen.add(pid); diff --git a/test/scripts/cron-mcp-cleanup-docker-client.test.ts b/test/scripts/cron-mcp-cleanup-docker-client.test.ts index 113ea820a221..3cbfde817aca 100644 --- a/test/scripts/cron-mcp-cleanup-docker-client.test.ts +++ b/test/scripts/cron-mcp-cleanup-docker-client.test.ts @@ -37,6 +37,20 @@ describe("cron MCP cleanup docker client", () => { } }); + it("does not parse malformed probe pid prefixes", async () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-cron-mcp-client-")); + try { + const pidPath = path.join(root, "probe.pid"); + fs.writeFileSync(pidPath, "123abc\n", "utf8"); + + const startedAt = Date.now(); + await expect(waitForProbePid(pidPath, { pollMs: 1, timeoutMs: 20 })).resolves.toBeUndefined(); + expect(Date.now() - startedAt).toBeLessThan(1000); + } finally { + fs.rmSync(root, { force: true, recursive: true }); + } + }); + it("accepts cron finished events only when the run status is ok", () => { expect(() => assertCronFinishedOk({ status: "ok" })).not.toThrow(); expect(() => assertCronFinishedOk({ status: "error" })).toThrow(