In GitHub Actions, what are the rules for a schedule: cron trigger?
answer
- Five fields, one fixed clock
- There is no timezone key
- Five minutes is the floor
- One branch decides what runs
- Public repos go quiet after two months idle
basics
~20 sFive-field POSIX cron, always interpreted in UTC, with a shortest usable interval of five minutes. Scheduled runs always use the workflow file and latest commit on the default branch, and start times are best-effort, not guaranteed.
solid answer
~50 s`schedule` takes a list of `- cron: '...'` entries using standard five-field POSIX cron syntax — minute, hour, day-of-month, month, day-of-week. The expression is **always UTC**; there is no timezone key, so daylight-saving shifts move your job by an hour twice a year unless you account for it. The shortest interval GitHub honours is five minutes. A scheduled run is not tied to any branch you choose: GitHub uses the workflow file and the latest commit on the **default branch**, which is why a cron added on a feature branch never fires. Start times are best-effort and can be delayed during peak load, so avoid `0 * * * *` and offset to an odd minute. Scheduled workflows in a public repository are disabled automatically after 60 days without repository activity, and are disabled in forks by default.
code
yaml · 14 lineson:
schedule:
- cron: '17 3 * * 1-5' # 03:17 UTC, Mon-Fri (offset off the hour)
- cron: '0 */6 * * *' # every six hours
workflow_dispatch: # reproduce a nightly failure on demand
jobs:
nightly:
if: github.repository == 'acme/service' # do not run in forks
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- run: ./gradlew integrationTestgo deeper
Recall the five-field cron shape, that the entry must be quoted in YAML, and that GitHub Actions interprets the expression in UTC with no timezone option.
Explain the mechanics that bite: the five-minute floor, the default-branch rule for which file and commit run, and why schedule accepts no branch or path filters.
Show operational judgment — offsetting off the hour, never assuming exact start times, monitoring for a nightly job that has stopped, and guarding forks with a repository check.
Own the strategy: which work belongs on a scheduler at all versus event-driven or external triggers, how many scheduled workflows an org can run before contention and cost matter, and how silent-failure detection is built.
## The syntax ``` on: schedule: - cron: '17 3 * * 1-5' - cron: '0 */6 * * *' ``` `schedule` is a **list**, so a workflow can carry several cron expressions. Each entry is a standard five-field POSIX cron string: minute (0–59), hour (0–23), day-of-month (1–31), month (1–12 or names), day-of-week (0–6 or names, where both 0 and 7 are Sunday). The operators are `*` (any), `,` (list), `-` (range) and `/` (step). The value must be quoted, because a bare `*` starts a YAML alias and will not parse. There is no `timezone` key. GitHub Actions evaluates every cron expression in **UTC**, full stop. A job you want at 09:00 in a European office is `0 8 * * *` in winter and `0 7 * * *` in summer — pick one and accept the hour of drift, or schedule twice and gate on the date, or simply schedule at a time where the shift does not matter. Candidates who claim they set a timezone are remembering a different CI system. ## Interval floor The shortest interval GitHub honours is **five minutes**. `*/1 * * * *` does not give you a run every minute; it is throttled. If you need sub-five-minute reactions you are outside what the scheduler is for — use an external trigger that dispatches the workflow, or a long-running process outside Actions. ## Which code runs This is the mechanic most people get wrong. A scheduled run has no ref you can choose: GitHub runs the workflow **as defined on the default branch**, against the **latest commit of the default branch**. Consequences: - Adding a `schedule:` block on a feature branch produces nothing until it is merged. - Editing the cron on a feature branch changes nothing about the live schedule. - `github.ref` inside a scheduled run is the default branch's ref. - You cannot schedule a job "for the release branch" through the trigger; you would check that branch out explicitly inside the job, or use `workflow_dispatch` for ref-specific runs. `schedule` also accepts no `branches`, `tags` or `paths` filters — there is no ref or diff to filter on. ## Timing is best-effort A scheduled workflow is queued when capacity allows, not at a guaranteed instant. Delays are common at the top of the hour, when a very large share of the world's crons fire simultaneously; a run scheduled for `0 * * * *` may start noticeably late, and under heavy load a scheduled run can be dropped rather than merely delayed. Two practical rules follow: never write a workflow that assumes exact start time (compute windows from an actual timestamp, not from the cron), and offset your minute — `17 3 * * *` rather than `0 3 * * *` — which measurably improves punctuality by avoiding the crowd. ## Automatic disabling GitHub disables scheduled workflows in a **public** repository after **60 days** with no repository activity, to stop abandoned repos consuming shared capacity. The Actions tab shows the workflow as disabled with a button to re-enable it; a commit or other activity also revives it. Separately, when a public repository is **forked**, scheduled workflows are disabled in the fork by default — this is why a fork does not start emailing its owner about your nightly job. In a private repository the 60-day rule does not apply the same way, but the practical lesson stands: a silent nightly job is one you should monitor, because "it stopped running" is a failure mode that produces no failure notification. ## Guarding forks and pairing with dispatch Two idioms belong on nearly every scheduled workflow. First, a fork guard, so a fork that re-enables Actions does not run your maintenance job against your assumptions: ``` jobs: nightly: if: github.repository == 'acme/service' ``` Second, a companion `workflow_dispatch:` under the same `on:` block, so you can reproduce a nightly failure immediately instead of waiting a day. Both are cheap and both come up in interviews as "what would you add to this file". ## What schedule is good for Nightly integration suites too slow for a pull request, dependency-freshness or licence audits, cache warm-ups, stale-issue sweeps, expiring-credential checks, and reporting. What it is *not* good for: anything requiring exact timing, anything needing sub-five-minute frequency, and anything that must run against a non-default branch. Recognising those three boundaries is the substance of the question.
- You added a schedule to a workflow on a feature branch and it never fired. Why?Scheduled runs always execute the workflow file as it exists on the repository's **default branch**, against that branch's latest commit. A cron on any other branch is inert until merged. There is no way to point `schedule` at a different ref; if you need a specific branch, check it out inside the job or use `workflow_dispatch`, which lets the caller choose the ref.
- Why do experienced teams avoid scheduling at 0 * * * * in GitHub Actions?Scheduled start times are best-effort. An enormous share of crons fire on the hour, so runs scheduled at minute zero queue behind the crowd and start late — occasionally late enough to matter, and under heavy load a run can be skipped entirely. Offsetting to an arbitrary minute such as `17` avoids the peak, and workflows should compute time windows from an actual timestamp rather than assuming the nominal one.
- How would you keep a nightly workflow from running in forks of a public repository?GitHub already disables scheduled workflows in forks by default, but a fork owner can re-enable them. Add a repository guard on the job — `if: github.repository == 'owner/repo'` — so even an enabled fork exits immediately. This matters most for jobs that post comments, open issues, or touch shared external systems.
saying these in an interview costs you the question
- Says you can set a timezone on the cron entry
- Expects every-minute scheduling to work
- Assumes the run uses the branch where the cron was added
- Treats the scheduled start time as exact
- Never notices a scheduled workflow that silently stopped