Cron: guard missing expr in schedule parsing
This commit is contained in:
@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Ollama: preserve unsafe integer tool-call arguments as exact strings during NDJSON parsing, preventing large numeric IDs from being rounded before tool execution. (#23170) Thanks @BestJoester.
|
||||
- Cron/Gateway: keep `cron.list` and `cron.status` responsive during startup catch-up by avoiding a long-held cron lock while missed jobs execute. (#23106) Thanks @jayleekr.
|
||||
- Gateway/Config reload: compare array-valued config paths structurally during diffing so unchanged `memory.qmd.paths` and `memory.qmd.scope.rules` no longer trigger false restart-required reloads. (#23185) Thanks @rex05ai.
|
||||
- Cron/Scheduling: validate runtime cron expressions before schedule/stagger evaluation so malformed persisted jobs report a clear `invalid cron schedule: expr is required` error instead of crashing with `undefined.trim` failures and auto-disable churn. (#23223) Thanks @asimons81.
|
||||
- TUI/Input: enable multiline-paste burst coalescing on macOS Terminal.app and iTerm so pasted blocks no longer submit line-by-line as separate messages. (#18809) Thanks @fwends.
|
||||
- TUI/Status: request immediate renders after setting `sending`/`waiting` activity states so in-flight runs always show visible progress indicators instead of appearing idle until completion. (#21549) Thanks @13Guinness.
|
||||
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
|
||||
|
||||
@@ -13,6 +13,18 @@ describe("cron schedule", () => {
|
||||
expect(next).toBe(Date.parse("2025-12-17T17:00:00.000Z"));
|
||||
});
|
||||
|
||||
it("throws a clear error when cron expr is missing at runtime", () => {
|
||||
const nowMs = Date.parse("2025-12-13T00:00:00.000Z");
|
||||
expect(() =>
|
||||
computeNextRunAtMs(
|
||||
{
|
||||
kind: "cron",
|
||||
} as unknown as { kind: "cron"; expr: string; tz?: string },
|
||||
nowMs,
|
||||
),
|
||||
).toThrow("invalid cron schedule: expr is required");
|
||||
});
|
||||
|
||||
it("computes next run for every schedule", () => {
|
||||
const anchor = Date.parse("2025-12-13T00:00:00.000Z");
|
||||
const now = anchor + 10_000;
|
||||
|
||||
@@ -41,7 +41,11 @@ export function computeNextRunAtMs(schedule: CronSchedule, nowMs: number): numbe
|
||||
return anchor + steps * everyMs;
|
||||
}
|
||||
|
||||
const expr = schedule.expr.trim();
|
||||
const exprSource = (schedule as { expr?: unknown }).expr;
|
||||
if (typeof exprSource !== "string") {
|
||||
throw new Error("invalid cron schedule: expr is required");
|
||||
}
|
||||
const expr = exprSource.trim();
|
||||
if (!expr) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -186,4 +186,19 @@ describe("cron schedule error isolation", () => {
|
||||
expect(badJob.state.lastError).toMatch(/^schedule error:/);
|
||||
expect(badJob.state.lastError).toBeTruthy();
|
||||
});
|
||||
|
||||
it("records a clear schedule error when cron expr is missing", () => {
|
||||
const badJob = createJob({
|
||||
id: "missing-expr",
|
||||
name: "Missing Expr",
|
||||
schedule: { kind: "cron" } as unknown as CronJob["schedule"],
|
||||
});
|
||||
const state = createMockState([badJob]);
|
||||
|
||||
recomputeNextRuns(state);
|
||||
|
||||
expect(badJob.state.lastError).toContain("invalid cron schedule: expr is required");
|
||||
expect(badJob.state.lastError).not.toContain("Cannot read properties of undefined");
|
||||
expect(badJob.state.scheduleErrorCount).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,4 +33,13 @@ describe("cron stagger helpers", () => {
|
||||
expect(resolveCronStaggerMs({ kind: "cron", expr: "0 * * * *", staggerMs: 0 })).toBe(0);
|
||||
expect(resolveCronStaggerMs({ kind: "cron", expr: "15 * * * *" })).toBe(0);
|
||||
});
|
||||
|
||||
it("handles missing runtime expr values without throwing", () => {
|
||||
expect(() =>
|
||||
resolveCronStaggerMs({ kind: "cron" } as unknown as { kind: "cron"; expr: string }),
|
||||
).not.toThrow();
|
||||
expect(
|
||||
resolveCronStaggerMs({ kind: "cron" } as unknown as { kind: "cron"; expr: string }),
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,5 +41,7 @@ export function resolveCronStaggerMs(schedule: Extract<CronSchedule, { kind: "cr
|
||||
if (explicit !== undefined) {
|
||||
return explicit;
|
||||
}
|
||||
return resolveDefaultCronStaggerMs(schedule.expr) ?? 0;
|
||||
const expr = (schedule as { expr?: unknown }).expr;
|
||||
const cronExpr = typeof expr === "string" ? expr : "";
|
||||
return resolveDefaultCronStaggerMs(cronExpr) ?? 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user