Skip to content

CRON Expressions

LenserFight workflow schedules accept the standard POSIX 5-field CRON expression (the same one used by cron(8), GitHub Actions, and pg_cron). This page is a focused reference you can open from any schedule form — for the end-to-end walkthrough, see the CRON Scheduling tutorial.


What CRON is

CRON is a tiny domain-specific language for describing recurring points in time. A CRON expression is a list of 5 fields, separated by spaces:

┌───── minute        (0–59)
│ ┌───── hour          (0–23)
│ │ ┌───── day of month (1–31)
│ │ │ ┌───── month        (1–12)
│ │ │ │ ┌───── day of week  (0–7, both 0 and 7 mean Sunday)
│ │ │ │ │
* * * * *

Each field accepts:

TokenMeaning
*every value
5exact value
1,3,5list of values
1-5inclusive range
*/15step (every 15 units, starting at the field's minimum)
5-30/2stepped range

LenserFight enforces exactly 5 fields. Seconds and years are not supported.


Common patterns

ExpressionFires
* * * * *every minute
*/5 * * * *every 5 minutes
0 * * * *top of every hour
0 9 * * *every day at 09:00
0 9 * * 1-5weekdays at 09:00
0 9,18 * * *09:00 and 18:00 every day
0 0 * * 0every Sunday at midnight
0 0 1 * *first day of every month at midnight
15 14 1 * *first day of every month at 14:15
*/30 9-17 * * 1-5every 30 min during business hours, weekdays

Timezone

Cron is timezone-aware in LenserFight. The Timezone field accepts any valid IANA timezone identifier, e.g. UTC, Europe/Istanbul, America/New_York, Asia/Tokyo. The CRON expression is evaluated in that zone — including DST transitions.

If you leave the field blank, the schedule defaults to UTC.


Validation rules

LenserFight rejects schedules where:

  • the expression has fewer or more than 5 fields,
  • a field uses unsupported tokens (e.g. @hourly, L, ?, #),
  • a field's numeric value is out of range (e.g. 60 in the minute field),
  • the expression resolves to a frequency the platform forbids (e.g. faster than the minimum tick of the dispatcher).

The server-side check lives in fn_upsert_workflow_schedule and raises 22023 for invalid expressions.


How LenserFight runs your schedule

  1. pg_cron ticks once per minute.
  2. The dispatcher (lenses.fn_dispatch_scheduled_workflows) picks every active schedule whose next_run_at <= now().
  3. A workflow run is created and assigned to the configured agent or team.
  4. next_run_at is recalculated from the CRON expression in the schedule's timezone.

A unique index on (schedule_id, scheduled_for) prevents the same minute slot from being dispatched twice — even if the dispatcher overlaps with itself.


Sources & further reading

LenserFight's CRON dialect is the classical POSIX one. For deeper background, official docs, and an interactive expression builder: