Your cron expression can be valid and still never run
A cron parser can answer one question—does this have five fields and legal tokens?—while your scheduler needs a different answer: will this job ever run, and will it run when I intended?
That gap is where silent cron failures live. A schedule can be syntactically valid, return no parser error, and still be impossible, surprisingly broad, or operationally noisy. Here is a small semantic checklist you can run before deploying a schedule.
1. Check the calendar, not only the grammar
Consider:
0 0 30 2 *
This has the right five-field shape: minute, hour, day of month, month, day of week. But February has no day 30. A syntax-only validator can label it valid, while a calendar-aware validator should tell you that its approximate frequency is never.
That distinction is important in CI: treat a parse error as a broken input, but treat an impossible calendar match as a review failure. Both deserve attention, but they need different messages.
The same idea applies to leap days. 0 0 29 2 * is meaningful, but it does not fire in non-leap years. Whether that is correct depends on the job; the validator should surface the edge case instead of silently deciding for you.
2. Be explicit about day-of-month/day-of-week semantics
This expression is a classic source of surprises:
0 0 1,15 * 1
Many traditional cron implementations treat a restricted day-of-month and a restricted day-of-week as an OR, not an AND. In that model, the job runs on the 1st, the 15th, or Monday. Someone reading the expression as “the 1st or 15th when it is Monday” will get a different schedule.
This is not a universal rule across every scheduler, so check the documentation for the runtime that will execute the job. The useful validation behavior is to warn whenever both fields are restricted and force the author to choose the intended semantics.
For example, a lightweight pre-deployment check can start like this:
function semanticWarnings(expression) {
const fields = expression.trim().split(/\s+/);
if (fields.length !== 5) {
return [{ level: 'error', message: 'Expected five cron fields' }];
}
const [minute, hour, dayOfMonth, , dayOfWeek] = fields;
const warnings = [];
if (dayOfMonth !== '*' && dayOfWeek !== '*') {
warnings.push({
level: 'high',
message: 'Confirm whether day-of-month and day-of-week are OR or AND',
});
}
if (minute === '0' && hour === '0') {
warnings.push({
level: 'info',
message: 'This runs at midnight; check for a synchronized load spike',
});
}
return warnings;
}
This is deliberately not a complete cron parser. It is a policy layer that catches questions a grammar check cannot answer. Keep it alongside a real parser rather than replacing one with string comparisons.
3. Explain stepped schedules as a sequence
*/7 * * * * is valid, but it is not the same thing as “every seven minutes” in the wall-clock sense. Its minute values are:
0, 7, 14, 21, 28, 35, 42, 49, 56
The next hour starts at minute 0 again. The final gap in the hour is four minutes, not seven. A live validation response for this expression reports exactly that uneven boundary.
This matters for polling, rate limits, and systems where evenly distributed work is the goal. If you need a true elapsed-time interval, a cron expression may be the wrong primitive; use a scheduler that supports interval-based execution or persist the next due time.
4. Treat midnight as an operational decision
0 0 * * * is easy to read, but it also puts every matching job at the same minute. A semantic validator can suggest a small offset—such as 2 0 * * *—when the workload does not require the exact boundary. That is not a correctness fix; it is a way to reduce avoidable synchronization.
Do not blindly rewrite schedules. Emit the suggestion, then let the owner decide whether the timing is contractual.
A practical validation pipeline
A practical review can use four stages when checking a schedule:
- Shape: split on whitespace and require five fields (or the exact format your runtime supports).
- Token validity: validate ranges, lists, steps, names, and special forms with a real cron parser.
- Calendar reachability: check restricted dates against the selected months and leap-year behavior.
- Operational intent: flag OR/AND ambiguity, uneven steps, synchronized boundaries, and unexpectedly high frequency.
The response should preserve these as separate findings. Returning only valid: true throws away the most useful part of the analysis. A better result has a boolean for parse validity plus structured warnings, observations, and suggestions, so CI can fail on high-severity findings while a human can review informational ones.
Test the same contract in CI and production
Keep the endpoint or library under test configurable so the same checks run against a local build and the deployed service:
export API_BASE='https://your-api.example'
response=$(curl -fsS --get "$API_BASE/api/cron/validate" \
--data-urlencode 'expr=0 0 30 2 *')
node -e '
const result = JSON.parse(process.argv[1]);
if (result.valid !== true) process.exit(1);
const text = JSON.stringify(result);
if (!text.includes("never (impossible schedule)")) process.exit(2);
' "$response"
The assertion above is intentionally narrow: it checks that the expression parses but is still identified as impossible. Add equivalent cases for your scheduler’s day-field semantics and any extensions it supports.
The key lesson is simple: syntactic validity is a starting point, not a deployment approval. A useful cron validator explains what the schedule means, whether it can reach a real date, and which operational assumptions deserve review.
If you want to try these checks against the hosted implementation, use the Cron Validator API.
Top comments (0)