Skip to main content

Chapter 5 of 7 · Time and numbers

The scheduler and background jobs

From hooks.py to a running job, and why one stays silent.

Reviewed 14 Oct 20263 min readFrappe v16.52.0

The scheduler does not run your job. It puts the job on a queue, and a worker runs it. Three separate things can be off: the scheduler, the worker for that queue, and the job's own registration.

Pick what you see below, and the module walks the seven checks, each with the command that tests it.

Try it

What do you see?

Pick the symptom. The seven checks come in the order that fits it.

What do you see?

  1. Is the job registered as a Scheduled Job Type?Start here

    scheduled_job_type.py:221–293

    Open Scheduled Job Type and search for the function's dotted pathIf not bench --site SITE migrate. Check the path imports: migrate prints a warning for one that does not.

  2. Is the site's scheduler switched on?

    scheduler.py:150–177

    Run If not bench --site SITE scheduler enable, then scheduler resume. Turn maintenance mode off.

  3. Is a scheduler process running?

    scheduler.py:38–75

    Run If not Start bench schedule, or its supervisor program or container service.

  4. Is the job Stopped?

    scheduler.py:132–147

    Open Scheduled Job Type → StoppedIf not Untick Stopped.

  5. Is it due?

    scheduled_job_type.py:90–127

    Run If not Read Last Execution and the cron line. The trigger command runs the job now, to prove the code.

  6. Is a worker listening to its queue?

    scheduled_job_type.py:187–188

    Run If not Start a worker for default and long: bench worker --queue long. A full queue refuses new jobs.

  7. Did it fail or run past its timeout?

    scheduled_job_type.py:143–185

    Open Scheduled Job Log (status, details) and Error LogIf not Fix the exception, or move the job to a Long frequency (1,500 s instead of 300 s).

0 of 7 ticked as fine

Frappe v16.52.0; the same frequencies, queues and timeouts hold on v15.123.0. SITE is your site’s name.

How does a line in hooks.py become a running job?

You add a function to scheduler_events in hooks.py. Nothing happens until bench migrate, which turns each entry into a row in Scheduled Job Type. The scheduler is one process, started by bench schedule or by its supervisor or container service. It holds a file lock so that only one runs, and it wakes every four minutes, aligned to the clock.

At each wake it skips a site that is in maintenance mode, paused, disabled, or has Enable Scheduler unticked in System Settings. For every job that is not stopped and is due, it puts a message on a queue. A worker takes it, runs the function, commits if it finished and rolls back if it raised. Jobs on every frequency except All and Cron also write a Scheduled Job Log row; those two write one only if Create Log is ticked.

Which queue, and how long does the job get?

Frappe has three queues: short and default allow 300 seconds, long allows 1,500. Scheduled jobs go to default, except frequencies called Long or Maintenance, which go to long. The "All" frequency runs every few minutes, set by scheduler_interval (240 seconds, so every four, by default). A job is not queued again while its earlier run is still waiting in the queue.

A queue refuses new jobs once it holds 500, plus 50 per site (max_queued_jobs changes the 500). A job that hits a deadlock or a lock timeout is retried up to five times. ERPNext's own jobs show the pattern: stock reposting on a 15-minute cron line, reminders hourly, most daily work under daily_maintenance, and deferred revenue under monthly_long.

Why did my job not run?

Seven checks, in the order that finds the cause fastest. bench doctor shows the scheduler and the workers at a glance.

  1. Is the site's scheduler on? bench --site SITE scheduler status.
  2. Is a scheduler process running?
  3. Is the job registered? Open Scheduled Job Type and search for the function. If it is missing, you changed hooks.py and did not migrate, or the dotted path is wrong: migrate prints a warning for a path it cannot import.
  4. Is it Stopped?
  5. Is it due? Last Execution and the cron line say. bench --site SITE trigger-scheduler-event your.module.function runs it now.
  6. Is a worker listening to its queue? bench --site SITE show-pending-jobs.
  7. Did it fail or hit its timeout? Scheduled Job Log and Error Log hold the traceback.

Is "Process Not Found" an outage?

Not always. The System Health Report checks the scheduler by trying to take the same file lock the scheduler holds. Where the web and scheduler containers do not share that file, the check cannot see the lock and reports Process Not Found while the scheduler runs. Read Scheduled Job Log freshness instead.

Where this comes from

Read at Frappe v16.52.0 and ERPNext v16.50.0. “(v15)” marks Frappe v15.123.0 and “(ERPNext v15)” marks ERPNext v15.122.0. Each link opens that file at that tag, on that line.

Official page: Background Jobs, read 14 October 2026. Case: the scheduler that shows Process Not Found in Docker.

Your next step

See it live

Run a sprint

Finish a task in this week's sprint, log the hour it took, and see the sprint's burndown, all on ERPNext's own records.

You end with hours booked on a task · 2 screens · about 3 min

No sign-up, no card, no account.

Next step · Architecture guide

Now see how we build a Frappe app

A separate app in your own Git, so upgrades keep working.