Server
The runner is a dedicated Node application — not the box's system crontab. It watches the
promoted content root, works out which files are jobs from their names, and each time a job is due it
loads the module and runs it to completion. Because it owns timing, overlap and logging itself, a job
behaves identically wherever the runner runs.
The schedule filename grammar
An entry point is a top-level file whose name ends .cron.js and contains
a schedule keyword — .every, .daily or .hourly — with a time
token. Nested files are never entry points, whatever they are called.
| Pattern | Runs | Example |
|---|---|---|
<name>.every.<N>ms.cron.js | every N milliseconds | poll.every.500ms.cron.js |
<name>.every.<N>s.cron.js | every N seconds | ping.every.30s.cron.js |
<name>.every.<N>m.cron.js | every N minutes | digest.every.30m.cron.js |
<name>.every.<N>h.cron.js | every N hours | sync.every.6h.cron.js |
<name>.every.<N>d.cron.js | every N days | prune.every.7d.cron.js |
<name>.daily.<HH>.cron.js | daily at HH:00 | report.daily.09.cron.js |
<name>.daily.<HHMM>.cron.js | daily at HH:MM | backup.daily.0300.cron.js |
<name>.daily.<HHMMSS>.cron.js | daily at HH:MM:SS | chime.daily.083000.cron.js |
<name>.hourly.<MM>.cron.js | at MM past every hour | sweep.hourly.15.cron.js |
<name>.hourly.<MMSS>.cron.js | at MM:SS past every hour | tick.hourly.3000.cron.js |
Intervals take any positive integer N. Clock tokens are zero-padded and range-checked:
HH is 00–23, MM and SS are
00–59. The Schedules reference is the
definitive table.
Base name & the single-instance rule
The base name is the part of the filename before the schedule keyword. For
report.hourly.30.cron.js the base name is report. The base name is the job's
identity, and it is the mutex key.
At most one process per base name runs across the whole runner at a time. If a tick fires while a run with the same base name is still active, that tick is skipped — never queued, never stacked. A slow run therefore stretches the effective cadence rather than piling copies on top of one another.
Twice an hour: duplicate the file
There is no comma syntax for multiple times. To run something at both :00 and
:30, write two files that share a base name:
report.hourly.00.cron.js → at :00 past every hour
report.hourly.30.cron.js → at :30 past every hour
Both resolve to the base name report, so the single-instance rule spans the pair: the
:30 copy is skipped if the :00 run is somehow still going. Duplicating for
multiple times is safe precisely because the copies share a mutex key and can never overlap.
The script module contract
A cron script is an ordinary Node module. On each tick the runner loads it and runs it to completion. You can export an async function — the runner awaits it — or just do work at the top level.
// digest.every.30m.cron.js
module.exports = async (ctx) => {
const rows = await collectSince(ctx.firedAt);
if (ctx.signal.aborted) return; // asked to stop early
await sendDigest(rows);
};
The ctx passed in (proposed) carries:
| Field | Meaning |
|---|---|
name | The job's base name — report, digest. |
firedAt | The scheduled time this tick represents, as a Date. |
signal | An AbortSignal the runner trips on shutdown so a long
job can bail out cleanly. |
A run is done when the exported promise settles (or, for top-level work, when the process exits). A thrown error, a rejected promise, or a non-zero exit is a failed run — logged with its output. See the Scripts reference for the exact semantics.
Supporting modules vs entry points
Only top-level, schedule-named files are ever invoked as jobs. Everything else in the folder — shared
libraries, helpers, sub-folders, vendored modules — syncs alongside and is imported with ordinary
require/import. A file named lib/format.js or
helpers.js is never a job, even if you accidentally put a schedule keyword in a nested
path; the entry-point test only looks at top-level filenames.
Logs
Each run captures its stdout/stderr plus a record of start time, stop time,
duration and exit code, server-side. You can mirror those logs back to your machine with the client's
--pull-logs flag — exactly like sitehoster's access logs.
Open design questions
A handful of runtime decisions are still open. They are written up here so they can be reviewed against real behaviour rather than in the abstract.
See also
The reference pages are the definitive lookups this guide points at.