# Misfires and overlap

A **misfire** is a run that should have fired while cronpad couldn't fire it, usually because the server was down. The job's `misfire_policy` decides whether to run it late, run every missed one, or skip them. **Overlap** is when a new run is due while the previous run of the same job is still running or retrying; `overlap_policy` decides whether both proceed.

## Misfire policy

When cronpad starts, it checks every active job whose next fire time is in the past by more than the SLA threshold.

| `misfire_policy` | Behavior | Use it for |
|---|---|---|
| `fire_once` (default) | Run once now for the most recent missed occurrence. Older missed ones are recorded as `skipped`. | Most jobs: "make sure it ran at least once" (reports, syncs, cleanups) |
| `fire_all` | Run every missed occurrence, oldest first, up to 10 (`scheduler.misfire_fire_all_cap`); the rest are `skipped`. | Jobs where each occurrence matters (per-hour billing aggregation) |
| `skip` | Run none of them; record them as `skipped`; wait for the next future occurrence. | Jobs that are useless when late (a "good morning" notification at 15:00) |

Example: a job runs `every hour`; cronpad is down from 10:05 to 13:20.

| Policy | What happens at 13:20 |
|---|---|
| `fire_once` | One run for 13:00 starts now; 11:00 and 12:00 are `skipped`. Next run 14:00. |
| `fire_all` | Runs for 11:00, 12:00 and 13:00 start now, in order. Next run 14:00. |
| `skip` | 11:00, 12:00, 13:00 are `skipped`. Next run 14:00. |

Skipped runs appear in the run history so gaps are visible, and each late run is marked as an SLA breach.

:::note
A run that is late because cronpad is busy (more work due than workers) is not a misfire. It still runs, and its lag is recorded.
:::

## Overlap policy

| `overlap_policy` | Behavior |
|---|---|
| `allow` (default) | The new run starts on time, even if the previous run is still running or waiting to retry. |
| `skip_if_running` | If the previous run hasn't reached a final state (`succeeded` or `failed`), the new run is recorded as `skipped` with reason `previous_run_in_progress`. |

Use `skip_if_running` for jobs that must not run concurrently with themselves, such as a long data export that would conflict with itself.