fix(cron): fix timeout, add timestamp validation, enable file sync

Fixes #7667

Task 1: Fix cron operation timeouts
- Increase default gateway tool timeout from 10s to 30s
- Increase cron-specific tool timeout to 60s
- Increase CLI default timeout from 10s to 30s
- Prevents timeouts when gateway is busy with long-running jobs

Task 2: Add timestamp validation
- New validateScheduleTimestamp() function in validate-timestamp.ts
- Rejects atMs timestamps more than 1 minute in the past
- Rejects atMs timestamps more than 10 years in the future
- Applied to both cron.add and cron.update operations
- Provides helpful error messages with current time and offset

Task 3: Enable file sync for manual edits
- Track file modification time (storeFileMtimeMs) in CronServiceState
- Check file mtime in ensureLoaded() and reload if changed
- Recompute next runs after reload to maintain accuracy
- Update mtime after persist() to prevent reload loop
- Dashboard now picks up manual edits to ~/.openclaw/cron/jobs.json
This commit is contained in:
Tyler Yust
2026-02-03 06:12:07 -08:00
committed by Peter Steinberger
parent a749db9820
commit 3a03e38378
7 changed files with 128 additions and 13 deletions

View File

@@ -2,6 +2,7 @@ import type { CronJobCreate, CronJobPatch } from "../../cron/types.js";
import type { GatewayRequestHandlers } from "./types.js";
import { normalizeCronJobCreate, normalizeCronJobPatch } from "../../cron/normalize.js";
import { readCronRunLogEntries, resolveCronRunLogPath } from "../../cron/run-log.js";
import { validateScheduleTimestamp } from "../../cron/validate-timestamp.js";
import {
ErrorCodes,
errorShape,
@@ -82,7 +83,17 @@ export const cronHandlers: GatewayRequestHandlers = {
);
return;
}
const job = await context.cron.add(normalized as unknown as CronJobCreate);
const jobCreate = normalized as unknown as CronJobCreate;
const timestampValidation = validateScheduleTimestamp(jobCreate.schedule);
if (!timestampValidation.ok) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, timestampValidation.message),
);
return;
}
const job = await context.cron.add(jobCreate);
respond(true, job, undefined);
},
"cron.update": async ({ params, respond, context }) => {
@@ -116,7 +127,19 @@ export const cronHandlers: GatewayRequestHandlers = {
);
return;
}
const job = await context.cron.update(jobId, p.patch as unknown as CronJobPatch);
const patch = p.patch as unknown as CronJobPatch;
if (patch.schedule) {
const timestampValidation = validateScheduleTimestamp(patch.schedule);
if (!timestampValidation.ok) {
respond(
false,
undefined,
errorShape(ErrorCodes.INVALID_REQUEST, timestampValidation.message),
);
return;
}
}
const job = await context.cron.update(jobId, patch);
respond(true, job, undefined);
},
"cron.remove": async ({ params, respond, context }) => {