





















@@ -14,6 +14,7 @@ import {
1414import { HEARTBEAT_SKIP_LANES_BUSY, type HeartbeatRunResult } from "../../infra/heartbeat-wake.js";
1515import * as schedule from "../schedule.js";
1616import type {
17+CronAgentExecutionPhase,
1718CronAgentExecutionPhaseUpdate,
1819CronAgentExecutionStarted,
1920CronJob,
@@ -1606,87 +1607,108 @@ describe("cron service timer regressions", () => {
16061607}
16071608});
160816091609-it("clears the pre-execution watchdog when isolated cron reaches attempt dispatch (#81368)", async () => {
1610-vi.useFakeTimers();
1611-try {
1612-const store = timerRegressionFixtures.makeStorePath();
1613-const scheduledAt = Date.parse("2026-05-13T09:56:00.000Z");
1614-const cronJob = createIsolatedRegressionJob({
1615-id: "isolated-attempt-dispatch-81368",
1616-name: "attempt dispatch regression",
1617- scheduledAt,
1618-schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
1619-payload: { kind: "agentTurn", message: "work", timeoutSeconds: 1_200 },
1620-state: { nextRunAtMs: scheduledAt },
1621-});
1622-await writeCronJobs(store.storePath, [cronJob]);
1623-1624-vi.setSystemTime(scheduledAt);
1625-let now = scheduledAt;
1626-const started = createDeferred<void>();
1627-let abortObserved = false;
1628-const cleanupTimedOutAgentRun = vi.fn(async () => {});
1629-const state = createCronServiceState({
1630-cronEnabled: true,
1631-storePath: store.storePath,
1632-log: noopLogger,
1633-nowMs: () => now,
1634-enqueueSystemEvent: vi.fn(),
1635-requestHeartbeat: vi.fn(),
1636- cleanupTimedOutAgentRun,
1637-runIsolatedAgentJob: vi.fn(
1638-async ({
1639- abortSignal,
1640- onExecutionStarted,
1641- onExecutionPhase,
1642-}: {
1643-abortSignal?: AbortSignal;
1644-onExecutionStarted?: (info?: CronAgentExecutionStarted) => void;
1645-onExecutionPhase?: (info: CronAgentExecutionPhaseUpdate) => void;
1646-}) => {
1647-onExecutionStarted?.({
1648-jobId: "isolated-attempt-dispatch-81368",
1649-phase: "runner_entered",
1650-});
1651-onExecutionPhase?.({
1652-jobId: "isolated-attempt-dispatch-81368",
1653-phase: "attempt_dispatch",
1654-backend: "codex-app-server",
1655-});
1656-started.resolve();
1657-abortSignal?.addEventListener(
1658-"abort",
1659-() => {
1660-abortObserved = true;
1661-},
1662-{ once: true },
1663-);
1664-return await new Promise<never>(() => {});
1665-},
1666-),
1667-});
1668-1669-const timerPromise = onTimer(state);
1670-await started.promise;
1671-await vi.advanceTimersByTimeAsync(60_100);
1672-now += 60_100;
1673-expect(abortObserved).toBe(false);
1674-expect(cleanupTimedOutAgentRun).not.toHaveBeenCalled();
1675-1676-await vi.advanceTimersByTimeAsync(1_140_000);
1677-now += 1_140_000;
1678-await timerPromise;
1679-1680-const job = requireJob(state, "isolated-attempt-dispatch-81368");
1681-expect(abortObserved).toBe(true);
1682-expect(job.state.lastStatus).toBe("error");
1683-expect(job.state.lastError).toContain("job execution timed out");
1684-expect(job.state.lastError).toContain("attempt-dispatch");
1685-expect(cleanupTimedOutAgentRun).toHaveBeenCalledTimes(1);
1686-} finally {
1687-vi.useRealTimers();
1688-}
1689-});
1610+it.each([
1611+{
1612+phase: "attempt_dispatch",
1613+phaseText: "attempt-dispatch",
1614+id: "isolated-attempt-dispatch-81368",
1615+name: "attempt dispatch regression",
1616+},
1617+{
1618+phase: "context_assembled",
1619+phaseText: "context-assembled",
1620+id: "isolated-context-assembled-81368",
1621+name: "context assembled regression",
1622+},
1623+] satisfies Array<{
1624+phase: CronAgentExecutionPhase;
1625+phaseText: string;
1626+id: string;
1627+name: string;
1628+}>)(
1629+"clears the pre-execution watchdog when isolated cron reaches $phaseText (#81368)",
1630+async ({ phase, phaseText, id, name }) => {
1631+vi.useFakeTimers();
1632+try {
1633+const store = timerRegressionFixtures.makeStorePath();
1634+const scheduledAt = Date.parse("2026-05-13T09:56:00.000Z");
1635+const cronJob = createIsolatedRegressionJob({
1636+ id,
1637+ name,
1638+ scheduledAt,
1639+schedule: { kind: "at", at: new Date(scheduledAt).toISOString() },
1640+payload: { kind: "agentTurn", message: "work", timeoutSeconds: 1_200 },
1641+state: { nextRunAtMs: scheduledAt },
1642+});
1643+await writeCronJobs(store.storePath, [cronJob]);
1644+1645+vi.setSystemTime(scheduledAt);
1646+let now = scheduledAt;
1647+const started = createDeferred<void>();
1648+let abortObserved = false;
1649+const cleanupTimedOutAgentRun = vi.fn(async () => {});
1650+const state = createCronServiceState({
1651+cronEnabled: true,
1652+storePath: store.storePath,
1653+log: noopLogger,
1654+nowMs: () => now,
1655+enqueueSystemEvent: vi.fn(),
1656+requestHeartbeat: vi.fn(),
1657+ cleanupTimedOutAgentRun,
1658+runIsolatedAgentJob: vi.fn(
1659+async ({
1660+ abortSignal,
1661+ onExecutionStarted,
1662+ onExecutionPhase,
1663+}: {
1664+abortSignal?: AbortSignal;
1665+onExecutionStarted?: (info?: CronAgentExecutionStarted) => void;
1666+onExecutionPhase?: (info: CronAgentExecutionPhaseUpdate) => void;
1667+}) => {
1668+onExecutionStarted?.({
1669+jobId: id,
1670+phase: "runner_entered",
1671+});
1672+onExecutionPhase?.({
1673+jobId: id,
1674+ phase,
1675+backend: "codex-app-server",
1676+});
1677+started.resolve();
1678+abortSignal?.addEventListener(
1679+"abort",
1680+() => {
1681+abortObserved = true;
1682+},
1683+{ once: true },
1684+);
1685+return await new Promise<never>(() => {});
1686+},
1687+),
1688+});
1689+1690+const timerPromise = onTimer(state);
1691+await started.promise;
1692+await vi.advanceTimersByTimeAsync(60_100);
1693+now += 60_100;
1694+expect(abortObserved).toBe(false);
1695+expect(cleanupTimedOutAgentRun).not.toHaveBeenCalled();
1696+1697+await vi.advanceTimersByTimeAsync(1_140_000);
1698+now += 1_140_000;
1699+await timerPromise;
1700+1701+const job = requireJob(state, id);
1702+expect(abortObserved).toBe(true);
1703+expect(job.state.lastStatus).toBe("error");
1704+expect(job.state.lastError).toContain("job execution timed out");
1705+expect(job.state.lastError).toContain(phaseText);
1706+expect(cleanupTimedOutAgentRun).toHaveBeenCalledTimes(1);
1707+} finally {
1708+vi.useRealTimers();
1709+}
1710+},
1711+);
1690171216911713it("keeps state updates when cron next-run computation throws after a successful run (#30905)", () => {
16921714const startedAt = Date.parse("2026-03-02T12:00:00.000Z");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。