fix(cron): validate field value ranges in isValidCron#3233
Conversation
isValidCron only checked the SHAPE of each cron sub-field (regex), not the VALUE ranges. Expressions like '99 99 * * *', '0 0 32 * *' (no 32nd day), or '0 0 1 13 *' (no 13th month) were accepted as valid, stored as cron tasks, and then NEVER fired — a silent footgun with no feedback to the user. Add per-field range validation (minute 0-59, hour 0-23, DOM 1-31, month 1-12, DOW 0-7 where 0 and 7 both mean Sunday per POSIX). Every literal number in a value (N), range (N-M), or ranged-step (N-M/S) must fall within its field's range; step values (*/S and the /S suffix) must be positive integers. Bare '*' wildcards remain always-valid. AI disclosure: authored with AI assistance (Claude Code) and reviewed by a human before submission.
|
Nice catch. I think this should probably use an existing cron parser instead of extending the custom validator one case at a time. The current patch still leaves validation and matching with different semantics. Example: this PR accepts day-of-week I would rather make a library the source of truth for cron semantics, probably
Shape would be roughly: import { CronExpressionParser } from "cron-parser";
const SUPPORTED_CRON_RE = /^[\d\s*,*/-]+$/;
export function isValidCron(expr: string): boolean {
const trimmed = expr.trim();
const fields = trimmed.split(/\s+/);
if (fields.length !== 5) return false;
if (!SUPPORTED_CRON_RE.test(trimmed)) return false;
try {
CronExpressionParser.parse(trimmed, { strict: false });
return true;
} catch {
return false;
}
}Then If we want to keep this PR smaller, I would at least fix DOW Written by Cameron ◯ Letta Code |
|
Thanks for the review @just-cameron — agreed on all counts. The DOW I'll rework this to put Will push the rework to this PR. |
… + matching Rework letta-ai#3233 per review: instead of extending the hand-rolled validator one case at a time, put cron-parser behind both isValidCron and cronMatchesTime so validation and execution share one source of truth. - isValidCron: keep the product dialect gate (5 fields, numeric syntax only) then delegate to CronExpressionParser.parse. Rejects out-of-range values, reversed ranges (59-0), zero steps (0-59/0) — all previously accepted. - cronMatchesTime: ask cron-parser for the next occurrence after the previous minute and check it lands in the target minute. Fixes the long-standing mismatch where DOW 7 validated as Sunday but never matched Date.getDay() (0-6). Timezone/DST now come from the library; invalid timezones fall back to local (preserving previous behavior). - Drops ~170 lines of hand-rolled field/range/step matching logic. Adds cron-parser (^5.6.1) as a dependency. AI disclosure: authored with AI assistance (Claude Code) and reviewed by a human before submission.
|
Reworked per your feedback —
Drops ~170 lines of hand-rolled Tests (
I did not migrate |
Problem
isValidCrononly checked the shape of each cron sub-field (a regex), not the value ranges. Out-of-range expressions were accepted as valid, stored as cron tasks, and then silently never fired — a footgun with no feedback to the user.Fix
Add per-field value-range validation, in addition to the existing shape check:
Every literal number in a value (
N), range (N-M), or ranged-step (N-M/S) must fall within its field's range. Step values (*/Sand the/Ssuffix) must be positive integers (rejects e.g.0-59/0). Bare*wildcards remain always-valid.The validation is field-position aware, so a literal that's legal in one field but not another is handled correctly (e.g.
32is fine as a year-style value but rejected in DOM).Behavior change
Out-of-range cron expressions are now rejected at
letta cron add --cron "…"with the existing invalid-expression error, instead of being accepted and silently never firing. All previously-valid expressions remain valid (verified against the existing test suite + new boundary tests).Tests
Extended
isValidCroninsrc/cron/parse-interval.test.ts:0-60,1-32in DOM), step must be > 0 (0-59/0), out-of-range inside a comma list rejects the whole field0-59 0-23 1-31 1-12 0-7Verification
bun test src/cron/— 95 passbun run typecheck— cleanbun run check:boundaries,check:cycles,check:exported-functions— cleanAI disclosure
Authored with AI assistance (Claude Code) and reviewed by a human before submission.