




























@@ -1606,6 +1606,88 @@ describe("cron service timer regressions", () => {
16061606}
16071607});
160816081609+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+});
1690+16091691it("keeps state updates when cron next-run computation throws after a successful run (#30905)", () => {
16101692const startedAt = Date.parse("2026-03-02T12:00:00.000Z");
16111693const endedAt = startedAt + 50;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。