fix: stagger missed cron jobs on restart (#18925) (thanks @rexlunae)

This commit is contained in:
Peter Steinberger
2026-03-09 06:07:25 +00:00
parent 79853aca9c
commit 96d17f3cb1
3 changed files with 9 additions and 5 deletions

View File

@@ -70,6 +70,7 @@ Docs: https://docs.openclaw.ai
- Gateway/config restart guard: validate config before service start/restart and keep post-SIGUSR1 startup failures from crashing the gateway process, reducing invalid-config restart loops and macOS permission loss. Landed from contributor PR #38699 by @lml2468. Thanks @lml2468.
- Gateway/launchd respawn detection: treat `XPC_SERVICE_NAME` as a launchd supervision hint so macOS restarts exit cleanly under launchd instead of attempting detached self-respawn. Landed from contributor PR #20555 by @dimat. Thanks @dimat.
- Telegram/poll restart cleanup: abort the in-flight Telegram API fetch when shutdown or forced polling restarts stop a runner, preventing stale `getUpdates` long polls from colliding with the replacement runner. Landed from contributor PR #23950 by @Gkinthecodeland. Thanks @Gkinthecodeland.
- Cron/restart catch-up staggering: limit immediate missed-job replay on startup and reschedule the deferred remainder from the post-catchup clock so restart bursts do not starve the gateway or silently skip overdue recurring jobs. Landed from contributor PR #18925 by @rexlunae. Thanks @rexlunae.
- Cron/owner-only tools: pass trusted isolated cron runs into the embedded agent with owner context so `cron`/`gateway` tooling remains available after the owner-auth hardening narrowed direct-message ownership inference.
## 2026.3.7

View File

@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { CronService } from "./service.js";
import { createCronServiceState } from "./state.js";
import { setupCronServiceSuite } from "./service.test-harness.js";
import { runMissedJobs } from "./timer.js";
import { createCronServiceState } from "./service/state.js";
import { runMissedJobs } from "./service/timer.js";
const { logger: noopLogger, makeStorePath } = setupCronServiceSuite({
prefix: "openclaw-cron-",
@@ -406,8 +406,9 @@ describe("CronService restart catch-up", () => {
expect(staggeredJobs[1]?.state.nextRunAtMs).toBeGreaterThan(
staggeredJobs[0]?.state.nextRunAtMs ?? 0,
);
expect((staggeredJobs[1]?.state.nextRunAtMs ?? 0) - (staggeredJobs[0]?.state.nextRunAtMs ?? 0))
.toBe(5_000);
expect(
(staggeredJobs[1]?.state.nextRunAtMs ?? 0) - (staggeredJobs[0]?.state.nextRunAtMs ?? 0),
).toBe(5_000);
await store.cleanup();
});

View File

@@ -858,7 +858,9 @@ export async function runMissedJobs(
startupCandidates: [] as Array<{ jobId: string; job: CronJob }>,
};
}
const sorted = missed.toSorted((a, b) => (a.state.nextRunAtMs ?? 0) - (b.state.nextRunAtMs ?? 0));
const sorted = missed.toSorted(
(a, b) => (a.state.nextRunAtMs ?? 0) - (b.state.nextRunAtMs ?? 0),
);
const startupCandidates = sorted.slice(0, maxImmediate);
const deferred = sorted.slice(maxImmediate);
if (deferred.length > 0) {