Bragi Docs Help

Schedule Rules

A CRON expression can say "at 4am on the 1st" but it cannot say "at 4am on the last working day", it knows nothing of the holidays your business keeps, and it has nothing at all to say about which environments a Job should run in. The Schedule Rules picker beside the CRON field closes those gaps. It reads as a summary of what is set, and opens onto two branches of tickboxes:

  • Day - decides which of the days the CRON offers a run actually happens on

  • Runs on schedule in - limits the schedule to certain kinds of environment

The picker sits beside the CRON on the Job edit page and in the Change Schedule popup on the Jobs page, so a Job's whole schedule can be adjusted from either.

The rules apply only to scheduled runs; Run Now and API triggers are unaffected, and run when you ask them to in any environment.

Day

Rule

Effect

Working Day

A run landing on a non-working day is dropped, and the CRON's next occurrence is considered instead.

Last day of month

Each run moves to the last calendar day of the month it falls in.

Last working day of month

Each run moves to the last working day of the month it falls in.

The two month end rules reposition a run: they move it to a day the CRON could not name. Working Day does the opposite - it moves nothing and instead filters out the runs that fall badly. Which of the two you want decides everything else on this page, so it is worth being clear which you are reaching for.

A month end rule changes only the date. The time of day always comes from the CRON, so 30 6 1 * * with Last working day of month runs at 06:30 on the last working day. Only one rule can apply, so ticking one unticks any other. Leaving them all unticked keeps the dates the CRON gave, which is how every Job behaved before this setting existed.

Working Day

Each occurrence the CRON produces is checked against the working day calendar. If it lands on a working day it stands; if it does not, it is thrown away and the CRON's next occurrence is checked in its place. With a CRON that fires daily this reads as "skip the weekend": 0 4 * * * with Working Day does not run on Saturday or Sunday, so a Friday run is followed by one at 04:00 on Monday.

A CRON that can only ever fire on non-working days, such as 0 4 * * Sun, has nothing to run on. The Job is left unscheduled and an error is logged, the same as any other rule that cannot be satisfied.

Month end rules: the rule picks the day, the CRON picks the months

Once a month end rule is set the CRON's day-of-month stops mattering - it only decides which months run, and at what time:

Frequency

CRON

Every month

0 4 1 * *

Every quarter

0 4 1 3,6,9,12 *

Half yearly

0 4 1 6,12 *

Once a year

0 4 1 12 *

The CRON also controls how many times on the chosen day the Job runs. One firing twice a day, such as 0 4,16 * * *, runs twice at month end - at 04:00 and again at 16:00 - rather than once. If you want a single run, use a CRON that fires once a day.

The current month still runs even if the CRON's date within it has already gone by, so setting a month end rule up on the 15th schedules the run for the end of that month. Once a month's run has happened, the next one is the following month the CRON names. Working Day has no such back-tracking: it only ever looks forward from now, because it never moves a run onto a date the CRON had already passed.

What counts as a working day

Working Day and Last working day of month read the same calendar. Working days come from the dbo.dim_date table in the warehouse for the Job's own environment, where IsTradingDay = 1. Out of the box that means Monday to Friday. To have public holidays and company shutdowns honoured, populate IsTradingDay from your own holiday source - update_dim_date carries a commented section showing where to join it in.

Last day of month is the only rule that never reads dim_date, so a Job that needs the plain calendar month end costs nothing extra.

Runs on schedule in

The second branch of the picker is a small tree of the environment kinds a Job runs on its schedule in:

[x] All environments [x] Develop [x] Test [x] Production

All environments is ticked by default, which is how every Job behaved before this setting existed, and every existing Job is left that way by the upgrade. Ticking it ticks everything below, unticking it clears everything, and it unticks itself as soon as you untick any one child.

All environments really does mean all. A Job left on the default picks up new kinds of environment automatically - if a further environment definition is ever added, Jobs nobody has narrowed will run there without being touched. That is why a Job records the environments it is kept out of rather than the ones it runs in: a list of what it runs in would be a snapshot of the environments that existed the day it was written.

Untick all three and the Job never runs on its own schedule anywhere; the picker warns you when that happens.

Environments are matched on their definition, not their name or id, so the setting survives deployment: a Job restricted to Production keeps that restriction when promoted, instead of needing its schedule unpicked by hand in each environment. In an environment that is not selected the Job shows no next scheduled run, and the picker says so rather than leaving an unexplained blank.

A Job excluded here is only excluded from its own schedule. It can still be nested as a task in another Job, triggered through the Scheduler Web API, or started with Run Now.

Recipes

A month end rule decides which day of the month, and the CRON decides which months and at what time. Working Day leaves the CRON's own dates alone and only drops the ones that fall badly. Change the 4 to run at a different hour.

You want

CRON

Day

Every working day

0 4 * * *

Working Day

Weekdays, minus the holidays in dim_date

0 4 * * Mon-Fri

Working Day

Last calendar day of every month

0 4 1 * *

Last day of month

Last working day of every month

0 4 1 * *

Last working day of month

Last calendar day of each quarter

0 4 1 3,6,9,12 *

Last day of month

Last working day of each quarter

0 4 1 3,6,9,12 *

Last working day of month

Last working day of the half year

0 4 1 6,12 *

Last working day of month

Last working day of the year

0 4 1 12 *

Last working day of month

Quarter end, but only in Production

0 4 1 3,6,9,12 *

Last working day of month

For the last one, also untick every environment except Production.

Things to watch

  • Working Day drops runs, it does not defer them. A run the calendar rejects is gone; the next one is whenever the CRON next fires on a working day. On a sparse CRON that can mean skipping a month, a quarter, or everything.

  • A month with no working day at its end is passed over. If the calendar shows an unbroken closure of more than a month running up to month end, that month is skipped and the next one the CRON names is used. If nothing within two years works, the Job is left unscheduled and an error is logged rather than the scheduler searching forever.

  • Everything in the picker deploys with the Job. The deploy preview flags a change to any of it just as it flags a change to the CRON. Each environment reads its own dim_date, so a Test warehouse without holidays loaded will schedule differently to Production.

02 October 2026