---
title: Run if missed (cron catch-up)
---

# Run if missed (cron catch-up)

## What it is

Omniscio's cron scheduler only fires a job when Omniscio is actually running at the scheduled minute. If your laptop was asleep, Omniscio was closed, or you were away for a few days, a job scheduled for "every day at 9 AM" simply doesn't run for the days Omniscio was off — by default Omniscio notices the missed fire, writes a log note, and quietly moves the schedule forward to the next future run **without** running it. (That default is deliberate: it stops a job that was missed hundreds of times from firing hundreds of times the moment you open the app.)

**Run if missed** is a **per-job toggle, off by default**, that changes that one decision for the job you turn it on for: when Omniscio was closed across the job's scheduled time, the job runs **once** the next time you open Omniscio, catching up misses from the **last 7 days**.

This is the catch-up behavior Windows Task Scheduler calls "Run task as soon as possible after a scheduled start is missed" — now available on Omniscio's own cron jobs, so you get Omniscio's friendly schedule presets, AI-by-chat scheduling, inbox failure alerts, and self-healing without having to split your automations between Omniscio and Task Scheduler.

## Where to find it

It is not a global switch — it belongs to one job. Open the Cron Jobs view from the Cron row in
the sidebar, click the job you want, and Run if missed sits in that job's editor alongside the
other per-job toggles. Tick it and save; the setting is stored with the job and travels with it.

## How it behaves

### How to use it

1. **Open the job's editor.** In Omniscio's **Cron Jobs** virtual project, click any job to bring up its editor dialog. "Run if missed" lives below the other per-job toggles in the same form.
2. **Flip on "Run if missed."** The row is labelled **Run if missed** with the description "If Omniscio was closed at the scheduled time, run once on next open (catches up misses from the last 7 days)." Off by default — existing jobs keep their current behaviour until you opt in.
3. **Save.** That's it. The next time you open Omniscio after a missed fire, the job runs once within about a minute of launch.

The toggle is **hidden for one-time jobs** ("run once at a specific date/time"). A one-off has no recurring schedule to resume, so catch-up doesn't apply to it — only **recurring** and **limited** (run-N-times) jobs show the toggle.

### What to expect

- **One catch-up run, not one per missed day.** However many scheduled fires were missed while Omniscio was closed — three days of a daily job, a whole weekend of an every-5-minutes job — the job runs **exactly once** on next open. All the missed windows collapse into that single catch-up, then the schedule resumes normally from the next future time. So the "an every-minute job would fire hundreds of times" worry doesn't happen.
- **Only misses from the last 7 days catch up.** If a job was missed longer ago than a week (an automation that lay dormant while you were away for two weeks), it is **not** caught up — it just resumes on its normal schedule, same as the default. The 7-day window covers a long weekend or a normal vacation without resurrecting something stale.
- **It respects everything a normal run respects.** The catch-up run goes through Omniscio's normal scheduled-run path, so:
  - **Approval still applies.** If the job requires approval, the catch-up run waits in your inbox for your tap, exactly like a normal fire — it is never auto-fired around the gate.
  - **It won't double-run.** Omniscio's at-most-once guards apply, so a catch-up can't fire the same job twice.
  - **Cost applies once.** For paid jobs (ones that run a recipe or spawn a Claude session), the catch-up is one billable run, not one per missed window. If you opt several paid jobs into catch-up and you've been away a while, expect each to fire once when you open Omniscio — that's the intended behavior of opting in.

## For agents

### How it works (for the curious / for agents)

When Omniscio starts, the cron engine runs `recomputeAllNextRuns()` ([src/main/services/cron/cron-recovery.ts](../../src/main/services/cron/cron-recovery.ts)). For every active job whose `next_run_at` is now in the past (a fire window elapsed while Omniscio was closed):

- **Default (catch-up off):** advance `next_run_at` to the next future fire and log a "missed while Omniscio was closed (not re-fired)" warning. (This is the long-standing F129 anti-thundering-herd behavior.)
- **Catch-up on** (and the job is recurring/limited, and the miss is within 7 days): **leave `next_run_at` in the past.** That's the entire mechanism — nothing re-fires the job directly. Because the cursor is left in the past, the engine's next 60-second tick selects the job via `listDueCronJobs` and runs it once through the ordinary dispatch path. When that run starts, the dispatch claim (`claimCronJobFire` → `getNextRun(now)`) advances the cursor to the next **future** fire, which is what collapses every missed window into the single catch-up run.

Because catch-up is just "let the normal tick fire it," the approval gate (`listDueCronJobs` only returns jobs whose `approval_status` is `not_required` or `approved`), the per-job in-flight guards, retries, and the claim-before-dispatch idempotency all apply with no extra code. See [cron-self-healing.md](cron-self-healing.md) and [create-cron-job-with-ai.md](create-cron-job-with-ai.md) for the surrounding cron machinery.

### The cursor claim must not advance before the run row

The whole catch-up mechanism above depends on one ordering inside the engine: **the durable cursor
advance (`claimCronJobFire`) happens AFTER the run row is created, never before it.** Both halves of
recovery need that. `listDueCronJobs` selects on `next_run_at IS NOT NULL AND next_run_at <= asOf`,
and `recomputeAllNextRuns` skips `one_off` outright — so a cursor advanced at selection time leaves a
fire that is neither selectable nor re-armable, and a run row is the only thing
`jobsToResumeAfterRestart` can find.

This was broken once, on 2026-09-28, by hoisting the claim into the tick's critical section to stop
the tick racing itself. Everything above stayed written as it is now while the code no longer matched
it, so a crash between the claim and the run row silently lost a one-off reminder with nothing
failing loudly enough to say so. The self-race is closed instead by a **process-local reservation**
taken inside the critical section (`tickReservedJobs` in
[src/main/services/cron/cron-engine-service.ts](../../src/main/services/cron/cron-engine-service.ts)) —
it touches no durable state, so a crash while it is held advances nothing and the fire is simply
still due. `tests/integration/cron-claimed-fire-survives-restart.test.ts` fails if the claim moves
ahead of the run row again.

The toggle is backed by the `cron_jobs.catch_up_if_missed` column (per-job, default 0), surfaced as `CronJob.catchUpIfMissed`, and is settable through both the in-app editor and the CLI control server's `POST /cron/jobs` + `PATCH /cron/jobs/:id` (so an external AI can turn it on when it creates or edits a job, approval-gated like any other CLI cron change).

## Related

The surrounding cron machinery — how jobs are created and what the engine's regular tick does — is
on the [create a cron job with AI](create-cron-job-with-ai.md) page. Its sibling opt-in, which
repairs a job after it fails rather than after it is missed, is
[cron self-healing](cron-self-healing.md), and what you see when a run fails for good is on
[cron failure alerts](cron-failure-alerts.md).

- [create-cron-job-with-ai.md](create-cron-job-with-ai.md) — how cron jobs are created and what the cron tick does in normal operation.
- [cron-self-healing.md](cron-self-healing.md) — the sibling per-job opt-in toggle that auto-fixes a job after it fails.
- [cron-failure-alerts.md](cron-failure-alerts.md) — the toast + inbox card when a cron job fails.
- [cron-session-jobs.md](cron-session-jobs.md) — the Session cron type that spawns a Claude session on schedule.