DEV Community

Cover image for Odoo Cron Jobs Failing Silently: How I Debugged and Fixed Background Tasks in Production
Aaron Jones
Aaron Jones

Posted on

Odoo Cron Jobs Failing Silently: How I Debugged and Fixed Background Tasks in Production

I recently ran into this issue in a production system, and after debugging it end-to-end, I realized that Odoo cron failures are usually silent by design unless we handle them explicitly.

This post walks through what actually goes wrong, why it happens, and how to fix it properly with code.

The Real Problem
Odoo cron jobs run in the background under the system user. When an exception occurs:

  • Odoo may rollback the transaction
  • The job execution stops
  • The error is not surfaced in the UI
  • The cron keeps rescheduling without doing any useful work This makes cron failures hard to detect and easy to ignore, especially in large systems.

Step 1: First Check If the Cron Is Even Running
Before touching code, verify the basics.

Go to:
Settings → Technical → Automation → Scheduled Actions

Check:

  • Is the cron Active?
  • Is the Next Execution Date updating?
  • Is numbercall exhausted?

If the next execution date never changes, the cron is not running at all.

Step 2: Enable Cron-Specific Logging (Often Missed)
By default, cron logs are easy to miss.

Update your odoo.conf:

log_level = info
log_handler = :INFO,odoo.addons.base.models.ir_cron:DEBUG

Enter fullscreen mode Exit fullscreen mode

Restart the server.
Now cron activity and failures will actually appear in logs.

Step 3: Why Crons Fail Silently (The Core Reason)
Most cron methods are written like normal business logic.

That’s the mistake.

Typical problematic code

def run_daily_job(self):
    orders = self.env['sale.order'].search([])
    for order in orders:
        order.process()

Enter fullscreen mode Exit fullscreen mode

If process() fails even once:

  • The entire cron crashes
  • No error is visible unless logging is perfect

Step 4: Fix It With Proper Error Handling (Mandatory)
Always wrap cron logic in try–except.

import logging
from odoo import models, api

_logger = logging.getLogger(__name__)

class SaleCron(models.Model):
    _inherit = 'sale.order'

    @api.model
    def run_daily_job(self):
        try:
            orders = self.search([])
            for order in orders:
                order.process()

            _logger.info("Sale cron executed successfully")

        except Exception as e:
            _logger.error(
                "Sale cron failed: %s", str(e), exc_info=True
            )

Enter fullscreen mode Exit fullscreen mode

This single change:

  • Prevents silent failures
  • Preserves stack traces
  • Makes debugging possible

Step 5: Prevent One Bad Record From Killing the Cron
A very common issue is one corrupted record stopping everything.

Use database savepoints.

@api.model
def run_daily_job(self):
    orders = self.search([])
    for order in orders:
        try:
            with self.env.cr.savepoint():
                order.process()
        except Exception as e:
            _logger.error(
                "Failed for order %s: %s", order.name, str(e)
            )

Enter fullscreen mode Exit fullscreen mode

Now:

  • One record fails → logged
  • Remaining records still process

Step 6: Avoid User-Dependent Code in Crons
Crons do not run as the logged-in user.

This often breaks code like:
self.env.user.email

Instead, explicitly fetch a known user:

admin = self.env.ref('base.user_admin')
email = admin.partner_id.email

Enter fullscreen mode Exit fullscreen mode

Never assume UI user context in background jobs.

Step 7: Make Sure the Cron Doesn’t Auto-Stop
If numbercall reaches 0, the cron never runs again.

Correct XML definition:

<record id="ir_cron_daily_sale_job" model="ir.cron">
    <field name="name">Daily Sale Processing</field>
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="state">code</field>
    <field name="code">model.run_daily_job()</field>
    <field name="interval_number">1</field>
    <field name="interval_type">days</field>
    <field name="numbercall">-1</field>
    <field name="active">True</field>
</record>

Enter fullscreen mode Exit fullscreen mode

-1 ensures the cron runs forever.

Step 8: Add Email Alerts for Production Safety
Logs are not enough in production.

def notify_admin(self, error):
    mail = self.env['mail.mail'].create({
        'subject': 'Odoo Cron Job Failed',
        'body_html': f'<p>{error}</p>',
        'email_to': 'admin@company.com',
    })
    mail.send()

Enter fullscreen mode Exit fullscreen mode

Trigger this inside the except block.

Step 9: Always Test Crons Manually
Before trusting a cron:
odoo shell -d your_database

env['sale.order'].run_daily_job()

If it fails here, it will fail in production.

Step 10: Watch for Database Locks (Hidden Cron Killers)
Long-running queries and locks can block cron execution.
SELECT * FROM pg_stat_activity WHERE state = 'active';

Slow ORM queries often look harmless but can freeze background jobs.

Key Takeaways
Odoo cron jobs don’t fail loudly by default.
They fail quietly, repeatedly, and dangerously.

The fix is not complex, but it must be intentional:

  • Always handle exceptions
  • Log aggressively
  • Use savepoints
  • Avoid user assumptions
  • Monitor cron health

Once these patterns are in place, cron jobs become predictable, observable, and safe for production.

Top comments (0)