Cron: preserve due jobs after manual runs (#23994)

This commit is contained in:
Tak Hoffman
2026-02-22 19:02:05 -06:00
committed by GitHub
parent bac26b4472
commit f6c2e99f5d
2 changed files with 44 additions and 1 deletions

View File

@@ -561,6 +561,46 @@ describe("Cron issue regressions", () => {
await runFinished.promise;
// Barrier for final persistence before cleanup.
await cron.list({ includeDisabled: true });
cron.stop();
});
it("does not advance unrelated due jobs after manual cron.run", async () => {
const store = await makeStorePath();
const nowMs = Date.now();
const dueNextRunAtMs = nowMs - 1_000;
await writeCronJobs(store.storePath, [
createIsolatedRegressionJob({
id: "manual-target",
name: "manual target",
scheduledAt: nowMs,
schedule: { kind: "at", at: new Date(nowMs + 3_600_000).toISOString() },
payload: { kind: "agentTurn", message: "manual target" },
state: { nextRunAtMs: nowMs + 3_600_000 },
}),
createIsolatedRegressionJob({
id: "unrelated-due",
name: "unrelated due",
scheduledAt: nowMs,
schedule: { kind: "cron", expr: "*/5 * * * *", tz: "UTC" },
payload: { kind: "agentTurn", message: "unrelated due" },
state: { nextRunAtMs: dueNextRunAtMs },
}),
]);
const cron = await startCronForStore({
storePath: store.storePath,
cronEnabled: false,
runIsolatedAgentJob: createDefaultIsolatedRunner(),
});
const runResult = await cron.run("manual-target", "force");
expect(runResult).toEqual({ ok: true, ran: true });
const jobs = await cron.list({ includeDisabled: true });
const unrelated = jobs.find((entry) => entry.id === "unrelated-due");
expect(unrelated).toBeDefined();
expect(unrelated?.state.nextRunAtMs).toBe(dueNextRunAtMs);
cron.stop();
});

View File

@@ -297,7 +297,10 @@ export async function run(state: CronServiceState, id: string, mode?: "due" | "f
emit(state, { jobId: job.id, action: "removed" });
}
recomputeNextRuns(state);
// Manual runs should not advance other due jobs without executing them.
// Use maintenance-only recompute to repair missing values while
// preserving existing past-due nextRunAtMs entries for future timer ticks.
recomputeNextRunsForMaintenance(state);
await persist(state);
armTimer(state);
});