Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Cron jobs (scheduling work to run on its own)

The Cron Jobs screen: how to create and manage a scheduled job by hand — the three job types (Script, Recipe, Session) and the fields each one takes, the schedule, approval and self-healing switches, the jobs dashboard with its search and grouping, per-job run history, and Run Now. The companion page covers handing a job's description to an AI to create. Also covers fire-time timing (a start can be up to ~2 minutes late under load) and the six alerts it can raise.

What it is

A cron job is something Omniscio does for you on a timetable — run a command, fire a saved recipe, or start a fresh Claude session — without you being there to press go. The Cron Jobs screen is where you create those jobs, see the ones you have, look at whether they actually ran, and fire one by hand when you do not want to wait for its next turn.

Every run is recorded, so "did the nightly job actually happen?" has an answer rather than a guess.

Where to find it

  • The Cron Jobs screen is a virtual project in the sidebar, headed Scheduled Tasks (Cron). It lists your jobs; + Add Job creates one, and there is a Search jobs… box.
  • Grouping and filtering sit above the list: group By <project> or By Frequency, and a Show completed checkbox to include the jobs that are finished rather than active.
  • A job is one row. Clicking it opens its detail view — the settings, its run history, and Run Now. The row itself also carries Run Now (hover it: "Run this job once right now."), which asks you to confirm first.
  • Creating or editing a job opens a New Cron Job / Edit Cron Job dialog, described below.

How it behaves

Creating a job

The dialog is a set of cards, top to bottom.

  • Name and Description — the name is what you will see in the list, so make it say what the job does.
  • Job Type — one of three, and the choice changes the rest of the form:
    • Script — run a shell command. You give it a Command and a Working Directory, and there is a Run in a visible window option for commands that need to show you something (most should stay hidden).
    • Recipe — run one of your saved recipes, chosen with a recipe picker. Use this when the work is already a multi-step workflow you maintain elsewhere.
    • Session — start a new Claude session with a prompt you write, optionally giving the session a fixed Session Name, and optionally setting a specific AI engine for it. This is the type to use when the scheduled work needs judgement rather than a fixed command.
  • Project — which project the job belongs to.
  • Visibility — whether a job that starts a session is silent, visible, or follows the recipe's own default. Silent sessions stay out of your attention path.
  • Schedule — when it fires.
  • Require Approval — when on, every run lands in your inbox as an approval before it happens. Use it for anything that costs money or touches something you would not want running unattended.
  • Self-Healing — whether a job that fails for good is handed to a fresh session to diagnose and fix; see Cron self-healing.
  • Run if missed — whether a fire that was missed while the app was closed runs late.
  • Environment Variables — extra variables for this job's runs only, stored encrypted.

Running a job after another one finishes

A job can be made to follow another instead of running on its own clock. In the editor this is the Run After card: Trigger After Job picks the job to follow — the card says plainly, "Run this job after another job completes." — and Trigger Condition decides when that counts.

Three conditions are offered, and the difference between the last two matters:

  • Success — fire only if the job it follows finished successfully. This is the default.
  • Failure — fire only if it failed. The usual use is a cleanup or a notification that should happen exactly when the main job breaks.
  • Always — fire whichever way it ended, success or failure.

Whichever you pick, it is judged on the job's final result: a job that retries is allowed to finish retrying first, so a chain never fires on a failure the job then recovered from.

The one thing to know before you chain two jobs together: nothing stops a loop. There is no limit on how deep a chain may go and no check that a chain has come back around to a job it already ran, so if you set A to follow B and B to follow A, the pair will keep firing each other for as long as both are enabled. Chaining is a plain "follow this one" link, not a guarded graph — so when you build a chain, satisfy yourself that it ends somewhere, and use the job's own enable/disable toggle to stop one if it does not.

Watching what a job did

Each job keeps a run history — one entry per firing, so you can see whether it ran, when, and how it ended, without reading logs. Beside it, Run Now fires the job immediately; it asks you to confirm first, because a job that spawns a session or sends something is not a free action.

Run history is kept for 30 days and then deleted, permanently. The app sweeps once a day and drops every run record older than that, so a job's history is a rolling month rather than a permanent log — and nothing in the app can bring a purged record back. 30 days is fixed; it is not tied to your data-retention setting and there is no way to raise it, so if you need a durable record of what a job did, have the job write it somewhere yourself rather than relying on the run list. The same 30-day sweep covers the app's other run-audit records — automation runs and their dead letters, the PR merge queue's runs, the PR reconcile ledger, the PR janitor's runs and dispositions, resolved recipe-approval requests, cron heal attempts, workflow-coach runs, and aggregation runs — so "a month" is the honest answer for how far back this class of history goes anywhere in Omniscio.

The jobs Omniscio adds for itself

Some jobs in the list you did not create. Omniscio maintains a handful of housekeeping jobs and seeds them into the same Cron Jobs list, each carrying a description that ends "(auto-managed)" so they are recognisable. They exist so the app can notice its own drift instead of waiting for a person to notice. On a machine where the thing they watch does not apply, they are never seeded at all — so you may have none of these, or several.

  • plugin-publish-drift-check — every 6 hours. Alerts when a marketplace plugin's repo has finished work that was never published.
  • firestore-rules-drift-check, firestore-indexes-drift-check, firebase-functions-drift-check — every 6 hours each. Alert when what is committed and what is deployed disagree, for rules, indexes and cloud functions respectively. The disagreement has two directions and opposite remedies, so the rules card names which one it found: the repo being ahead means deploy, while production being ahead means a deploy would DELETE the rules only live has — that card says "push" and prints no deploy command. On a non-default Firebase project these carry a -<project> suffix in their names.
  • cloud-billing-budget-drift-check — every 6 hours. Alerts when the cloud project has no GCP billing budget or alert set up, so a spend surprise cannot arrive silently.
  • worktree-triage-dispatch — every 30 minutes. Dispatches triage for worktrees whose work was left stranded.
  • master-sync-conflict-escalate — every 15 minutes. Finishes a conflicted sync of the shared branch: resolves what it can for free and buys a capped session for the rest. This one is seeded switched off; turn it on if you want it.
  • friction-triage-dispatch — every 2 hours. Dispatches triage for the agent-friction ledger.

These are ordinary jobs once seeded, so you can inspect, disable, re-schedule or delete any of them like one of your own. Be aware that deleting one is not permanent in the way you might expect: the seeding is idempotent and re-runs on startup, so the app will recreate a housekeeping job it still needs.

A scheduled fire can start late, on purpose

Three rate controls sit between "the schedule came due" and "the job started". All three exist because of one measured failure: a process-creation storm — a wave of sessions and two cron jobs starting processes 12ms apart — was enough to hang the machine. Spacing out starts is the fix, and the cost is that a fire's start time is not exactly its scheduled time.

  1. A per-job phase offset — up to 45 seconds. Before anything else, each scheduled fire waits an offset derived from its own job id (the id hashed into a window, default 45 seconds, capped at 59 so a fire stays inside its scheduled minute). This matters most for schedules like */5, where several jobs come due on the same tick every five minutes: without the offset they would all begin ramping at once. It is deterministic — a given job always fires at the same second of its minute, so it never drifts and nothing needs to be stored.
  2. A calm wait — up to 90 seconds, only while the machine is busy. If the whole computer is under heavy load (roughly 85% busy), the fire waits in 10-second steps until things calm down, up to a 90-second ceiling. It is a ceiling, not a skip: past it the job proceeds regardless, so a loaded machine delays a run and can never lose one.
  3. A serialising start throttle — 4 seconds. Two dispatch releases are spaced at least 4 seconds apart, so two due jobs can never start their process trees in the same instant.

What this looks like in practice. On an idle machine a job scheduled for 09:00 starts within its 09:00 minute, as you would expect (the phase offset is deliberately bounded to keep it there). On a loaded machine it can be pushed past the minute — a 45-second offset plus a 90-second calm wait is the worst case, so if you are comparing a log timestamp against your schedule, a start up to about two minutes late is normal behaviour under load, not a fault. If you need to know whether the scheduler is healthy rather than merely slow, the alerts below are the signal to read.

A manual Run now is never delayed

Run now dispatches the job directly rather than through the scheduled path, so none of the three controls above apply to it. It is never held behind a phase offset or a calm window.

Alerts the scheduler can raise

Six alerts are raised by the scheduler's own alert builders (cron-engine-alerts.ts), on top of the job-failure alert that has a page of its own (cron failure alerts) — so seven conditions can land in your inbox from cron, and every one of them means something different. A job that is slow, a job that is broken, and a scheduler that has stopped are three separate problems with three separate fixes, and before this list five of them had no published explanation at all.

Every one of these is deduped (one row per condition, updated rather than repeated) and every one clears itself when the condition goes away — so an alert disappearing is the all-clear.

Alert What it means What to do
A scheduled job fails (toast + card) A run failed, and self-healing is not taking it over. Its own page: cron failure alerts.
A scheduled job keeps timing out The job starts but never finishes — it has started and been stopped for timing out several times in a row, so it never completes. It is probably waiting on something that never responds. Open its schedule and review what it waits on, or turn it off.
Scheduled jobs have stopped running The scheduler itself is unhealthy: every recent tick threw. This is the broad one — it means backups, reports and automations may simply not be running. Usually a locked or busy local database. Restart Omniscio. It normally clears this, and the alert clears itself once the scheduler recovers.
A scheduled job has stopped running (overdue) The scheduled time passed and the job never ran, and its schedule has fallen behind. The job may be stuck, or the scheduler may need a restart. Open its schedule to review or turn it off. Clears when the job runs again.
A scheduled job keeps failing to start The job has failed to start on several recent checks — it is not running at all. Usually a bad setting: an environment variable that no longer resolves, or a project that no longer exists. Open its schedule and check its settings. Clears when the job starts again.
A scheduled job is paused until restart The job started, got stuck, and could not be stopped cleanly — so it was paused deliberately, to avoid running a duplicate of work that may already be under way. It will not run again until you restart Omniscio (or turn it off). Clears once it runs again.
Some automation actions could not be completed An automation's action permanently failed after every retry and was set aside rather than dropped. See automations and auto-replies.

Two of these are worth telling apart at a glance, because the fixes are different sizes: "Scheduled jobs have stopped running" is plural and about the scheduler (restart), while "A scheduled job has stopped running" is singular and about one job (inspect it). If several jobs are misbehaving at once, believe the plural one.

For agents

  • The screen is a virtual project; the editor is src/renderer/src/features/cron/JobEditorDialog.tsx, the list and dashboard CronJobsSidebar.tsx / CronDashboard.tsx, and a job's detail view with its run history JobDetailPanel.tsx.
  • The same jobs are creatable, editable, deletable, fireable and toggleable over the CLI control server at /cron/jobs (POST to create, PATCH/DELETE /cron/jobs/:id, POST /cron/jobs/:id/run, POST /cron/jobs/:id/toggle), all bearer-authed and capped at 10 mutations a minute, with a ceiling of 200 jobs.
  • Jobs created by an AI arrive approval-gated — requiresApproval is forced on for them, so an agent cannot quietly schedule work that runs without you.
  • Chaining is two columns on the job row: run_after_job_id (the job to follow) and run_after_trigger (success | failure | always, default success). The only guard is a durable at-most-once-per-parent-run check, which keys on this parent run — a fresh parent run gets a fresh id, so it does not break a cycle. Do not build a loop.

Related

Last verified 2026-10-01