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.

PatternRunsExample
<name>.every.<N>ms.cron.jsevery N millisecondspoll.every.500ms.cron.js
<name>.every.<N>s.cron.jsevery N secondsping.every.30s.cron.js
<name>.every.<N>m.cron.jsevery N minutesdigest.every.30m.cron.js
<name>.every.<N>h.cron.jsevery N hourssync.every.6h.cron.js
<name>.every.<N>d.cron.jsevery N daysprune.every.7d.cron.js
<name>.daily.<HH>.cron.jsdaily at HH:00report.daily.09.cron.js
<name>.daily.<HHMM>.cron.jsdaily at HH:MMbackup.daily.0300.cron.js
<name>.daily.<HHMMSS>.cron.jsdaily at HH:MM:SSchime.daily.083000.cron.js
<name>.hourly.<MM>.cron.jsat MM past every hoursweep.hourly.15.cron.js
<name>.hourly.<MMSS>.cron.jsat MM:SS past every hourtick.hourly.3000.cron.js

Intervals take any positive integer N. Clock tokens are zero-padded and range-checked: HH is 0023, MM and SS are 0059. 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:

FieldMeaning
nameThe job's base name — report, digest.
firedAtThe scheduled time this tick represents, as a Date.
signalAn 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.