fix(cron): treat missing enabled as true in update() (openclaw#15477) thanks @eternauta1337

Verified:
- pnpm exec vitest src/cron/service.issue-regressions.test.ts

Co-authored-by: eternauta1337 <550409+eternauta1337@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Alejandro Santander
2026-02-15 11:52:02 -03:00
committed by GitHub
parent a7b6555195
commit 9a344da298
5 changed files with 125 additions and 14 deletions

View File

@@ -233,6 +233,109 @@ describe("Cron issue regressions", () => {
await store.cleanup();
});
it("treats persisted jobs with missing enabled as enabled during update()", async () => {
const store = await makeStorePath();
const now = Date.parse("2026-02-06T10:05:00.000Z");
await fs.writeFile(
store.storePath,
JSON.stringify(
{
version: 1,
jobs: [
{
id: "missing-enabled-update",
name: "legacy missing enabled",
createdAtMs: now - 60_000,
updatedAtMs: now - 60_000,
schedule: { kind: "cron", expr: "0 */2 * * *", tz: "UTC" },
sessionTarget: "main",
wakeMode: "next-heartbeat",
payload: { kind: "systemEvent", text: "legacy" },
state: {},
},
],
},
null,
2,
),
"utf-8",
);
const cron = new CronService({
cronEnabled: true,
storePath: store.storePath,
log: noopLogger,
enqueueSystemEvent: vi.fn(),
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
});
await cron.start();
const listed = await cron.list();
expect(listed.some((job) => job.id === "missing-enabled-update")).toBe(true);
const updated = await cron.update("missing-enabled-update", {
schedule: { kind: "cron", expr: "0 */3 * * *", tz: "UTC" },
});
expect(updated.state.nextRunAtMs).toBeTypeOf("number");
expect(updated.state.nextRunAtMs).toBeGreaterThan(now);
cron.stop();
await store.cleanup();
});
it("treats persisted due jobs with missing enabled as runnable", async () => {
const store = await makeStorePath();
const now = Date.parse("2026-02-06T10:05:00.000Z");
const dueAt = now - 30_000;
await fs.writeFile(
store.storePath,
JSON.stringify(
{
version: 1,
jobs: [
{
id: "missing-enabled-due",
name: "legacy due job",
createdAtMs: dueAt - 60_000,
updatedAtMs: dueAt,
schedule: { kind: "at", at: new Date(dueAt).toISOString() },
sessionTarget: "main",
wakeMode: "now",
payload: { kind: "systemEvent", text: "missing-enabled-due" },
state: { nextRunAtMs: dueAt },
},
],
},
null,
2,
),
"utf-8",
);
const enqueueSystemEvent = vi.fn();
const cron = new CronService({
cronEnabled: false,
storePath: store.storePath,
log: noopLogger,
enqueueSystemEvent,
requestHeartbeatNow: vi.fn(),
runIsolatedAgentJob: vi.fn().mockResolvedValue({ status: "ok", summary: "ok" }),
});
await cron.start();
const result = await cron.run("missing-enabled-due", "due");
expect(result).toEqual({ ok: true, ran: true });
expect(enqueueSystemEvent).toHaveBeenCalledWith(
"missing-enabled-due",
expect.objectContaining({ agentId: undefined }),
);
cron.stop();
await store.cleanup();
});
it("caps timer delay to 60s for far-future schedules", async () => {
const timeoutSpy = vi.spyOn(globalThis, "setTimeout");
const store = await makeStorePath();