Khichdi InfoTech
Skip to content

Odoo Development · intermediate · 10 min read

Scheduled Actions and Cron Jobs

Designing reliable Odoo cron jobs: ir.cron configuration, idempotency, batching, failure handling, and alternatives to infinite server actions.

Last reviewed 2026-07-30 · Estimated effort: 2–6 weeks typical implementation slice

Table of contents

Scheduled actions (ir.cron) call a model method on an interval — minutes, hours, or days — using a specified user and optional priority. They run inside Odoo workers alongside HTTP and longpolling.

Cron is for modest batch work tied to Odoo data: mail queues, session vacuum, incremental sync markers. Heavy ETL or minute-level external API polling often belongs in external Python with Odoo RPC.

Reliable crons are idempotent, batched, logged, and bounded by time or record count. They respect company context and avoid holding locks across thousands of rows in one transaction.

Automated actions triggered from the UI are fine for demos; production schedules belong in versioned XML with code review and staging validation.

This article covers defining crons in modules, choosing intervals, implementing safe job methods, and operating them in multi-worker deployments.

XML record sets name, model_id, state=code, code=model.method(), interval_number, interval_type, numbercall (-1 for infinite), active, user_id, and priority. Method must exist on model and be callable without UI context.

Load cron data after models and ACL — the job user needs rights to touch target records. Document expected runtime so ops can spot stuck jobs.

  • doall=False skips missed runs after downtime — usually preferred
  • nextcall in data is optional; server recalculates on activate
  • priority lower number runs first when queue backs up

Process in chunks: search with limit, mark state, commit or sub-savepoint per batch. Use a watermark field (last_sync_at, processed_until_id) instead of rescanning entire tables.

Guard re-entry with ir.config_parameter lock or SELECT FOR UPDATE SKIP LOCKED pattern on a queue model when multiple workers could overlap.

Cron runs as user_id on the cron record — record rules apply. For multi-company, iterate companies with with_company(company) or sudo().with_company(company) when rules require elevation.

Email templates and translated messages need lang in context — set on env when generating customer-facing content from cron.

Log start/end counts at info level; log failures with stack traces. For critical jobs, write ir.logging rows or a custom job.log model visible to support.

Deactivate runaway crons in emergencies via UI or active=False in data with hotfix module. Prefer feature flags in config parameters over commenting code in production.

Sub-minute scheduling, GPU work, large file transforms, or retry-heavy external APIs — use queue_job (OCA), Celery, or cloud scheduler hitting Odoo API.

Odoo.sh and on-prem multi-worker setups still share one cron thread per worker behavior depending on version — verify docs for your hosting.

  • Make every cron safe to run twice — upsert by external id, not blind create.
  • Cap batch size and loop until no work remains instead of one massive transaction.
  • Set realistic intervals — hourly sync beats every minute hammering partner API.
  • Include cron user in security review and integration runbooks.
  • Test cron methods via shell and TransactionCase before relying on schedule.
  • Document how to manually trigger method from shell for support playbooks.

  • !Cron method raises on first bad row — entire batch rolls back silently.
  • !Running as superuser and forgetting record rules in production data mix.
  • !Unbounded search([]) on mail.message or stock.move history tables.
  • !Duplicating same cron via Studio and module — double execution.
  • !Long sleep() inside cron blocking worker thread pool.
  • !Assuming missed runs catch up with doall=True on heavy jobs — backlog storm.

Cron jobs are production code — batch them, log them, and assign a real user. Treat intervals and locks as part of the design, not ops afterthought.

When schedules outgrow Odoo workers, move orchestration outward and keep Odoo as system of record via APIs.

Cron tick → lock → search batch → process → watermark → release.
1

Request

HTTP / RPC enters Odoo.

2

Auth & ACL

Users, groups, record rules.

3

ORM work

Reads/writes with business logic.

4

Response

UI, API payload, or job enqueue.

Odoo cron vs queue_job vs external worker.
Standard fit?

Configure and train

Odoo covers ≥80% of the process

Unique process?

Custom module

Competitive advantage depends on it

External master?

Integrate

Another system owns the data

Unclear ROI?

Defer / pilot

Scope is speculative

Frequently asked questions

How do I run a cron manually?

Settings → Technical → Automation → Scheduled Actions → Run Manually, or call method from odoo shell with same user context.

Can one cron call another model?

Code field executes as Python on the model — use env['other.model'].method() inside. Keep indirection readable.

Why did my cron stop after upgrade?

Check active flag, model xml id rename, method rename, or missing ACL for cron user after security refactor.

Scheduled action vs automated action?

Scheduled = time-based ir.cron in code/XML. Automated action = ir.cron or triggers on create/write defined in UI — prefer XML/code for version control.

Unreliable nightly jobs or duplicate sync runs?

We redesign cron and queue patterns with idempotent batches and operator-visible job status.

Business technology and software delivery