Skip to content
Latchkey

Cron for a specific weekday or day of month in CI

Cron day targeting trips people up because when both day-of-month and day-of-week are set (not *), POSIX cron runs when either matches, not both. Leave one field as * unless you truly want the OR behavior.

What this error means

A cron meant for "the 1st, only if it is a Monday" instead runs on every 1st and every Monday, because both day fields were set at once.

.github/workflows/nightly.yml
# Intended: first Monday only. This runs on the 1st OR any Monday.
on:
  schedule:
    - cron: '0 9 1 * 1'

Common causes

Day-of-month and day-of-week are OR-ed

When both fields are specified, cron fires if either matches, which is rarely what "specific date and weekday" means.

Assuming AND semantics

People expect the two day fields to intersect, but POSIX cron unions them.

How to fix it

Set only one day field

  1. For a weekday, set day-of-week and leave day-of-month as *.
  2. For a day of month, set day-of-month and leave day-of-week as *.
  3. For combined logic, gate inside the job with a date check.
.github/workflows/nightly.yml
# Every Monday at 09:00 UTC
on:
  schedule:
    - cron: '0 9 * * 1'
# First of the month at 09:00 UTC
    - cron: '0 9 1 * *'

Combine date logic inside the job

For "first Monday" style rules, run daily and exit early unless a shell date check passes.

.github/workflows/nightly.yml
- run: |
    if [ "$(date -u +%u)" != "1" ] || [ "$(date -u +%d)" -gt 07 ]; then
      echo "not the first Monday"; exit 0
    fi

How to prevent it

  • Leave one day field as * to avoid OR surprises.
  • Put complex date logic in a job-level guard.
  • Test the expression against a cron calculator.

Frequently asked questions

What causes "cron day-of-week vs day-of-month"?
When both fields are specified, cron fires if either matches, which is rarely what "specific date and weekday" means.
How do I fix cron day-of-week vs day-of-month?
Set only one day field

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card