Settings — Scheduler
The Scheduler page is where every recurring job in Sublarr lives. It’s the operations console: see when each job last ran, when it’ll next fire, whether it succeeded, and force a run-now or pause without restarting the container. Backed by APScheduler with a SQLAlchemy job store, so jobs survive restarts with their next-fire-time intact.
What’s listed
Section titled “What’s listed”The page shows one row per registered job. Seventeen jobs ship out of the box. Several take their interval from a setting elsewhere, so your numbers can differ from the defaults below — the page always shows what is actually scheduled.
Library and queue
| Job | Default trigger | Effect |
|---|---|---|
| wanted_scanner | every 6 h | Walks the library and queues searches for items missing a target language. Interval follows Settings → Automation. |
| wanted_search | every 4 h | Processes the wanted queue — this is the one that talks to providers. Interval follows Settings → Automation. |
| subtitle_automation | every 2 min | Drains the subtitle-automation queue — extracting embedded tracks and translating local sidecars. By far the most frequent job; see Subtitle Automation. |
| standalone_scan | every 6 h | Scans standalone library sources that have no *arr behind them. |
| upgrade_scan | every 24 h | Looks for better replacements for subtitles you already have. |
Maintenance
| Job | Default trigger | Effect |
|---|---|---|
| cleanup | nightly 03:45 | Runs the cleanup rules: orphan delete, format upgrades, trash prune. |
| foreign_track_sweep | every 6 h | Works through the batched foreign-track removal; see Cleanup. Does nothing unless that rule is enabled. |
| subtitle_health_sweep | nightly 04:30 | Checks stored subtitles for damage; see Subtitle Health. |
| dubtitle_scan | nightly 04:15 | Detects dubtitles; see Dubtitle Detection. |
| repair_resume | every 10 min | Picks up an interrupted subtitle repair where it left off. |
| scheduler_history_cleanup | nightly 03:15 | Prunes scheduler history past the configured retention. |
| translation_events_cleanup | nightly 03:30 | Prunes recorded translation events past their retention. |
Data and reporting
| Job | Default trigger | Effect |
|---|---|---|
| anidb_sync | every 7 days | Refreshes the offline AniDB dump. |
| stats_rollup | nightly 04:00 | Aggregates the daily statistics the Statistics page reads. |
| mt_reseek | every 24 h | Re-checks machine-translated subtitles for a real one having appeared since. |
| provider_degradation_check | every hour | Watches for a provider that quietly stopped working; see Providers. Off unless you enable the alerts. |
| usage_stats_ping | every 24 h | Sends the anonymous usage ping, if you consented. See Usage Statistics. |
Plugins can register their own jobs; those appear in the list with a plugin:<name>: prefix on the job ID.
Per-row controls
Section titled “Per-row controls”| Column | Effect |
|---|---|
| Job ID | Unique identifier. Human-readable for built-in jobs. |
| Trigger | Current trigger expression — interval (every 6h) or cron (0 3 * * *). Edit inline. |
| Last run | Timestamp + outcome — see Outcomes. |
| Next run | When the next firing is scheduled. |
| Runtime (avg) | Median wall-clock time over the last 30 runs. |
| Run now | Triggers the job out-of-band. Respects per-job concurrency lock. |
| Pause | Suspends future firings without removing the job. |
| Edit trigger | Open the trigger editor — interval or cron. |
| History | Drawer with the last 100 runs and their outcomes. |
| Reset to default | Restore the trigger to the shipped default. Useful if a custom edit is causing issues. |
Trigger types
Section titled “Trigger types”| Type | Editor field | Example |
|---|---|---|
| Interval | hours / minutes / seconds | every 6h (every 6 hours) |
| Cron | minute, hour, day, month, day-of-week | 0 3 * * * (3:00 AM daily) |
Cron is preferred for “fire at a specific time of day” jobs (cleanup, anidb_sync). Interval is preferred for “fire every X” jobs (scanners, queue drains).
Run-now semantics
Section titled “Run-now semantics”Clicking Run now:
- Queues an immediate firing — bypasses the next scheduled time.
- Acquires the per-job lock — if a scheduled run is currently in-flight, the run-now waits.
- Records the run as
manual: truein history so you can distinguish from scheduled firings.
If a manual run-now collides with a scheduled tick, the second-arriving fire is recorded as skipped_overlap and not run twice.
Pause / resume
Section titled “Pause / resume”Pausing keeps the job in the registry but skips firings until resumed. Useful for:
| Scenario | Action |
|---|---|
| Maintenance window — don’t run the scanner | Pause wanted_scanner until done. |
| Provider rate-limit incident | Pause everything that calls providers. |
| Investigating a misbehaving job | Pause to observe state without further triggering. |
Paused jobs show a paused badge; their Next run is replaced with paused.
Job history
Section titled “Job history”The History drawer shows recent runs with full timing breakdown:
| Column | Effect |
|---|---|
| Started at | When the run began. |
| Finished at | When the run ended (or running if in-flight). |
| Duration | Wall-clock time. |
| Outcome | See Outcomes. |
| Items processed | Count of meaningful work units (varies per job). |
| Error | Truncated stack trace on failure; click for full. |
Per-job retention is configured via the scheduler_history_cleanup job (default 30 days).
Outcomes
Section titled “Outcomes”| Outcome | Meaning |
|---|---|
ok | The run finished. |
error | The run raised. The error column carries the reason. |
skipped_overlap | A previous run of the same job was still in flight, so this firing was dropped rather than run twice. |
timeout | The job exceeded its time budget, was asked to stop, and did stop at its next check point. |
timeout_abandoned | The job exceeded its budget, was asked to stop, and was still running when the scheduler gave up waiting. |
timeout_not_started | The job never ran. Every scheduler worker was busy for its whole allowance, so the firing waited in the queue and was dropped without executing a line. |
timeout_not_started is not a problem with the job named in the row — that job
did nothing at all. It means the scheduler’s worker pool was saturated, which
in practice is caused by other jobs that overran and kept running: an
abandoned run holds its worker for as long as its work continues. So a burst of
timeout_not_started rows usually points at whichever job has been logging
timeout_abandoned, not at the ones reporting it.
The remaining outcomes are worth separating. A background thread cannot be killed safely,
so cancellation is cooperative: long jobs check between work units and stop
there. A timeout therefore means the work really ended. A
timeout_abandoned means it did not — the run is no longer being waited on,
but the work may still be going, may still be writing files, and may still be
holding the job’s lock when the next firing arrives.
If you see timeout_abandoned repeatedly on the same job, the useful question
is not how long it was given but where it stopped checking. Cancellation
can only take effect at a check point, so a job whose remaining work has none
will overrun any budget you give it — raising the number then only delays the
same entry. The job’s own log lines around the timeout show what it was still
doing, and that is what identifies the missing check point.
Each job’s allowance is sized to fit one whole unit of its work, since that is
the granularity at which it can stop at all. A run that legitimately needs
longer than one unit to wind down is recorded as timeout, not
timeout_abandoned.
A run that was interrupted by the container stopping is recorded as
InterruptedByShutdown — expected after an update, not a fault.
Single-instance enforcement
Section titled “Single-instance enforcement”Sublarr is designed to run as a single primary instance. Running multiple replicas with the same database would cause every job to fire on every replica.
Set SUBLARR_SCHEDULER_ROLE=disabled on all replicas except the primary to prevent duplicate firings. The primary defaults to primary.
| Value | Behaviour |
|---|---|
primary (default) | Scheduler runs. |
disabled | Scheduler is suspended; replica only serves API/UI traffic. |
| anything else | Startup raises an error. |
Custom jobs
Section titled “Custom jobs”Plugins or operators with API access can register custom jobs via POST /api/v1/scheduler/jobs. Custom jobs require a Python callable resolvable at the configured module path; see Plugin Development for the registration pattern.
Health surface
Section titled “Health surface”The header shows aggregate health:
| Tile | Meaning |
|---|---|
| Total jobs | Registered jobs (built-in + plugin). |
| Active | Not paused, not in-flight. |
| Running | Currently firing. |
| Failed (last 24h) | Jobs with at least one failure in the last day. |
Three or more failures in a row on the same job triggers the scheduler_job_failed notification event.