# Scheduled Workflows

## Scheduled Workflows

Scheduled workflows let you run a workflow on a recurring **cron schedule** instead of (or in addition to) responding to repository events. This is useful for tasks like nightly validation runs, weekly housekeeping jobs, or periodic backups of generated artifacts.

### How It Works

To turn a workflow into a scheduled workflow, add a `schedule:` entry to its `on:` block with one or more cron expressions:

```yaml
name: Nightly Validation
on:
  schedule:
    - cron: '0 4 * * *'   # Every day at 04:00 UTC
jobs:
  validate:
    runs-on: windows10-S5K
    steps:
      - uses: actions/checkout@v4
      - run: echo "Running nightly validation..."
```

Copia evaluates due schedules **once per minute** and queues a new run for each schedule whose next fire time has elapsed. Scheduled runs are queued with the `schedule` event and use the latest commit on the repository's **default branch** as their checkout target.

{% hint style="info" %}
Scheduled workflows are only registered from workflow files on the **default branch**. Adding a `schedule:` trigger on any other branch has no effect until the change is merged.
{% endhint %}

#### Lifecycle

{% stepper %}
{% step %}

#### Registration

When you push a workflow file containing a `schedule:` trigger to the default branch, Copia parses it and registers the cron specs. Any previously registered schedules for the repository are replaced.
{% endstep %}

{% step %}

#### Firing

Once per minute, Copia looks for any schedule whose next fire time is in the past. For each match, it queues a new run against the default branch and advances the schedule to its next fire time.
{% endstep %}

{% step %}

#### Refresh

Each subsequent push to the default branch refreshes the registered schedules from the new commit. If a workflow's `schedule:` block is removed or the workflow file is deleted, that schedule is dropped on the next default-branch push.
{% endstep %}
{% endstepper %}

### Cron Syntax

Schedules use the standard five-field cron format:

```
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 or JAN–DEC)
│ │ │ │ ┌───── day of week (0–6 or SUN–SAT)
│ │ │ │ │
* * * * *
```

The following shorthand descriptors are also supported:

| Descriptor              | Equivalent to | Description                 |
| ----------------------- | ------------- | --------------------------- |
| `@yearly` / `@annually` | `0 0 1 1 *`   | Once a year on January 1    |
| `@monthly`              | `0 0 1 * *`   | On the first of every month |
| `@weekly`               | `0 0 * * 0`   | Every Sunday at midnight    |
| `@daily` / `@midnight`  | `0 0 * * *`   | Every day at midnight       |
| `@hourly`               | `0 * * * *`   | At the start of every hour  |

{% hint style="warning" %}
The smallest scheduling granularity is **one minute**. Sub-minute schedules are not supported.
{% endhint %}

#### Timezones

Cron expressions are evaluated in **UTC** by default. To pin a schedule to a different timezone, prefix the expression with `TZ=` (or `CRON_TZ=`) and a valid IANA timezone name:

```yaml
on:
  schedule:
    - cron: 'TZ=America/Chicago 0 6 * * 1-5'   # 6:00 AM Central, Mon–Fri
```

### Examples

#### Multiple schedules on one workflow

You can list more than one cron expression. The workflow will fire once for each schedule whose time has come.

```yaml
name: Periodic Checks
on:
  schedule:
    - cron: '*/15 * * * *'  # Every 15 minutes
    - cron: '0 0 * * 0'     # Plus a deeper sweep every Sunday at midnight UTC
jobs:
  ping:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Tick at $(date -u)"
```

#### Combining scheduled and event triggers

A scheduled trigger can coexist with normal event triggers in the same workflow.

```yaml
name: Build and Nightly Smoke Test
on:
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'   # Nightly at 02:00 UTC
jobs:
  smoke:
    runs-on: windows10-S5K
    steps:
      - uses: actions/checkout@v4
      - name: Run smoke tests
        run: ./scripts/smoke.sh
```

You can detect which trigger started a run via the `github.event_name` context variable, which will be set to `schedule` for scheduled runs:

```yaml
steps:
  - name: Send nightly report
    if: github.event_name == 'schedule'
    run: ./scripts/send-report.sh
```

#### Business-hours schedule with a custom timezone

```yaml
name: Hourly Health Check
on:
  schedule:
    - cron: 'TZ=America/New_York 0 9-17 * * 1-5'   # Top of every hour, 9 AM – 5 PM ET, weekdays
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - run: ./scripts/health-check.sh
```

### Notes and Limitations

* **Only the default branch is scheduled.** Schedules defined on feature branches are not active until merged.
* **Disabling Actions on a repository pauses its schedules.** They will not fire while Actions is disabled, but the schedule rows remain registered and will resume firing once Actions is re-enabled.
* **Archived repositories** do not fire scheduled workflows.
* **Missed runs are not backfilled.** If your Copia instance is down when a schedule was due to fire, that occurrence is skipped — the schedule will simply fire at its next scheduled time after the instance recovers.
* **Cron expressions are evaluated in UTC unless a `TZ=` prefix is provided.** Daylight saving transitions may cause a schedule to skip or repeat an hour in the affected timezone.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.copia.io/docs/actions/scheduled-workflows.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
