How the Bell reads cron
The twelve checks, and where each one comes from.
Chore Bell reads crontab text the way Vixie cron and its descendants (cronie on Red Hat-family servers, Debian's cron) read it. It never connects to a server. Times come from the IANA time zone database in PHP 8.3.
- R1
Not Linux cron
Linux crontab has five time fields: minute, hour, day of month, month, day of week (crontab(5)). A sixth time field (seconds or year) and the characters ? L W # belong to Quartz and other schedulers. Cron would take the sixth field as the start of the command, so the Bell stops and says so.
- R2
Field errors
Each field is a comma list of *, a number, or a range a-b, each optionally followed by /step (crontab(5)). Allowed values: minute 0-59, hour 0-23, day 1-31, month 1-12 or jan-dec, day of week 0-7 or sun-sat, where 0 and 7 are both Sunday. Ranges must run forwards; cron does not wrap 22-2 round midnight. Shortcuts: @reboot, @yearly, @annually, @monthly, @weekly, @daily, @midnight, @hourly.
- R3
Never runs
Calendar arithmetic: when the day-of-week field is * and no month in the month field has the listed day (30 or 31 February, 31 April, June, September, November), the line is valid but never runs.
- R4
Both day fields set
"If both fields are restricted (i.e., do not contain the * character), the command will be run when either field matches the current time" (crontab(5)). So 30 6 1 * 1 runs on the 1st AND on every Monday. Vixie cron and cronie treat a field that begins with * (like */2) as unrestricted for this rule; the Bell does the same.
- R5
Day 29, 30 or 31
Calendar arithmetic: a month without the listed day is skipped, not moved to its last day. Day 31 runs in 7 months a year; 29 February only in leap years.
- R6
A step without a range
Steps are written after * or a range (crontab(5): "Step values can be used in conjunction with ranges"). "5/10" has neither; Vixie cron refuses the line, and some newer crons read it as 5-59/10. The corrected crontab writes the range out.
- R7
An unescaped %
"A % character in the command, unless escaped with a backslash (\), will be changed into newline characters, and all data after the first % will be sent to the command as standard input" (crontab(5)). date +%F must be written date +\%F.
- R8
Programs started by name
Cron starts jobs with a short PATH (usually /usr/bin:/bin), not your login shell's. php, wp, composer, node, python and similar tools often live elsewhere, or come in several versions; the one cron finds may not be the one your site uses. Scripts named by a relative path (./clean.sh) are looked for in your home directory, where cron starts. Write full paths.
- R9
Output not redirected
Cron mails anything a job prints to the crontab owner, "or to the user specified in the MAILTO environment variable in the crontab, if such exists" (cron(8)); an empty MAILTO="" throws it away (crontab(5)). Every run of a chatty job is then one email, or a silent failure. The corrected crontab adds >/dev/null 2>&1 or a log file, as you choose.
- R10
Every minute
* * * * * is 1,440 runs a day. Some shared hosts set a minimum interval for cron jobs; the Bell only reminds you to check yours.
- R11
Runs that overlap
If you say how long a job usually runs and that is as long as or longer than the shortest gap between its starts, cron will start a second copy while the first is still running. The corrected crontab wraps the command in flock -n with a lock file in your home directory: a start is skipped while the last run still holds the lock.
- R12
Daylight Saving
From cron(8) (cronie): "Local time changes of less than three hours, such as those caused by the Daylight Saving Time changes, are handled in a special way. This only applies to jobs that run at a specific time and jobs that run with a granularity greater than one hour. Jobs that run more frequently are scheduled normally. If time was adjusted one hour forward, those jobs that would have run in the interval that has been skipped will be run immediately. Conversely, if time was adjusted backward, running the same job twice is avoided." The Bell finds every run in a skipped or repeated hour over the next 12 months for the zone you choose (from the time zone database), and calls a job "fixed-time" when neither its minute nor its hour field begins with *. Other schedulers (systemd timers, hosted cron services) may behave differently. A CRON_TZ line is read by cronie for the lines after it ("The CRON_TZ variable specifies the time zone specific for the cron table", crontab(5)); Debian's cron does not read it. The Bell always uses the zone you pick.
What the levels mean
- Stops itCron would refuse the line, run something else, or never run it.
- Fixed belowThe corrected crontab changes the line for you.
- Watch outIt runs, but maybe not when or how you expect; you decide.
- Good to knowNothing wrong, worth knowing.
Spread
When jobs start at the same minute, the spread option moves each job whose minute field is a single number to the minute of the hour with the fewest other starts across its hours (over a typical week), preferring minutes other than :00, :15, :30 and :45, then the nearest one. It never moves a job that already has a minute to itself.
What the Bell cannot know
What your host has really scheduled, which cron program your server runs, your host's own limits, and whether a command works. Test a changed job by hand before you rely on it.