Saltar al contenido principal

Odoo MissingError: el registro no existe: cómo solucionarlo (2026)

Pulsa MissingError: ¿El registro no existe (id=X) en Odoo? Diagnóstico paso a paso y arreglo permanente con checklist de prevención para el 18/17/19 en producción.

E
ECOSIRE Research and Development Team
|4 de mayo de 20267 min de lectura1.2k Palabras|

Este artículo actualmente está disponible solo en inglés. La traducción estará disponible próximamente.

You open a record in Odoo and the UI throws a red error banner. The server log shows a familiar Python traceback ending with this line:

You open a record in Odoo and the UI throws a red error banner. The server log shows a familiar Python traceback ending with this line:

odoo.exceptions.MissingError: Record does not exist or has been deleted.
(Record: sale.order(457,), User: 12)

This is one of the most common runtime errors in Odoo 17.0, 18.0, and 19.0. It surfaces in views, automated actions, scheduled crons, and JSON-RPC calls. The error itself is usually correct — the record genuinely is gone — but the reason it disappeared (or appears to have disappeared) is rarely obvious. This guide walks through the fix, the root cause, the diagnostic playbook, and the guardrails to stop it recurring.

Quick Fix

If a single user is blocked right now, ask them to refresh the browser tab and clear the breadcrumbs. Most MissingError instances come from a stale UI pointing at a record another user (or a workflow) deleted seconds ago. If the error happens in code you control, wrap the offending read with exists():

order = self.env['sale.order'].browse(order_id)
if not order.exists():
    return  # or raise UserError(_("Order no longer exists."))
order.action_confirm()

That single line removes 90 percent of MissingError exceptions from custom modules.

Why This Happens

MissingError is raised by BaseModel._check_recordset_validity() whenever Odoo tries to read or write a record whose database row has been deleted, archived in a way that violates a related-field rule, or is no longer accessible to the current user under the active record rules. The root causes fall into four buckets:

  1. Race condition between two users. User A opens a quotation in their browser. User B deletes it. User A clicks "Confirm" — the form sends the now-orphaned id to the server.
  2. Cascade deletion. A parent record (for example a res.partner) is deleted with ondelete='cascade' and quietly takes a child sale order with it. A scheduled cron then tries to invoice that sale order.
  3. Multi-database / multi-company drift. A scheduled action reads ids that exist in database A but is executing against database B (common in staging/restore mistakes).
  4. Record rules. The record is not deleted, but the current user lost access to it via a record rule (ir.rule) — Odoo treats inaccessible records as missing rather than raise AccessError in some contexts.

Knowing which bucket you are in is the entire job. Bucket 4 is the trap most teams fall into; they assume the row was deleted and start restoring backups.

Step-by-Step Diagnosis

Run these checks in order before changing any code.

1. Confirm the row really is gone. Connect to PostgreSQL and check:

SELECT id, write_date, create_uid, write_uid
FROM   sale_order
WHERE  id = 457;

If you get zero rows, the record was deleted. If you get one row, it is a record-rule problem — skip to step 4.

2. Find who deleted it and when. Odoo's audit log (base.automation activity, or mail.message with mail_activity_type_id) often records deletions. For unlogged deletions, check the PostgreSQL pg_stat_activity and replication logs if you have them. The audit_log community module is worth installing on production from day one.

3. Check for cascade deletions. Inspect the model definition for foreign keys with ondelete='cascade':

order_id = fields.Many2one('sale.order', ondelete='cascade')

If a parent of your missing record was deleted, your record went with it.

4. Test record-rule access. As the affected user, run:

# In Odoo shell — odoo-bin shell -c odoo.conf
self.env['sale.order'].with_user(user_id).search([('id', '=', 457)])

If this returns an empty recordset but sudo() returns the record, you have a record-rule problem, not a deletion.

5. Check for stale browser state. Ask the user to hard-refresh (Ctrl+Shift+R). If the error stops happening, it was a browser-cache or client-tab race.

Permanent Fix

For each bucket, the durable fix differs:

Bucket 1 — Race condition. Always call .exists() before acting on an externally provided id. In particular, every server action, automated action, and webhook handler must do this:

def _process_webhook(self, payload):
    order = self.env['sale.order'].browse(int(payload['order_id']))
    order = order.exists()
    if not order:
        _logger.warning("Webhook for missing order %s", payload['order_id'])
        return {'status': 'ignored'}
    order.write({'note': payload['note']})

Bucket 2 — Cascade deletion. Audit your ondelete declarations. Production systems should default to ondelete='restrict' for business records and ondelete='set null' for soft references. Reserve ondelete='cascade' for true child rows (lines on an order, attachments on a record).

Bucket 3 — Cross-database drift. Scheduled crons should validate self.env.cr.dbname matches the expected database name, and any external system pushing ids should include a database identifier in the payload.

Bucket 4 — Record rules. Add an explicit AccessError early so the user gets a meaningful message:

order = self.env['sale.order'].browse(order_id)
if not order.exists():
    if self.env['sale.order'].sudo().browse(order_id).exists():
        raise AccessError(_("You do not have access to this order."))
    raise UserError(_("This order has been deleted."))

How to Prevent It

  • Lint rule. Add a custom Pylint check that flags any browse(int) not followed by .exists() within ten lines. We ship this rule with every Odoo module ECOSIRE delivers.
  • Default ondelete='restrict'. Make this the team default for all new Many2one fields. Override only with a written reason in the field's docstring.
  • Audit log on production. Install auditlog (OCA) and turn on logging for any model touched by external integrations. You will save hours every time you debug a missing record.
  • Soft delete for high-value records. Add active = fields.Boolean(default=True) and prefer archiving over hard deletion. MissingError becomes impossible because the row is still there.
  • Idempotent webhooks. External callers should retry. Your handler should treat "row gone" as success-with-warning, not failure.

Frequently Asked Questions

Does MissingError mean the record was definitely deleted?

No. It means Odoo cannot read the record under the current user, in the current cursor, on the current database. Record rules, multi-company filters, archived parent records, and database mix-ups can all surface as MissingError. Check PostgreSQL directly before assuming a deletion happened.

How do I recover a record that was deleted by mistake?

If you have point-in-time PostgreSQL backups (which every production Odoo should), restore the base backup to a temporary database, dump only the missing rows, and re-import. If you do not have backups, check mail.message for any payload that might contain the lost data — Odoo logs many record changes there. ECOSIRE's Odoo support and maintenance team handles this kind of recovery routinely.

Why does the same code work in development but throw MissingError in production?

Almost always a timing issue. Development databases are small, transactions commit fast, and you are usually the only user. Production has concurrent users, longer transactions, and crons running in parallel. Add .exists() checks, and the race goes away.

Is there a way to log every MissingError automatically?

Yes. Add a custom logging handler in odoo.conf for the odoo.exceptions logger at level INFO, and pipe it to your monitoring stack. Sentry's Odoo integration (or self-hosted GlitchTip) is the cleanest way — every MissingError becomes an event with a full traceback and you can spot patterns by user, model, and time.


Need help with a tricky Odoo error? ECOSIRE's Odoo experts have shipped 215+ modules — get expert help.

Compartir:
E

Escrito por

ECOSIRE Team

Technical Writing

The ECOSIRE technical writing team covers Odoo ERP, Shopify eCommerce, AI agents, Power BI analytics, GoHighLevel automation, and enterprise software best practices. Our guides help businesses make informed technology decisions.

ECOSIRE

Transforme su negocio con Odoo ERP

Implementación, personalización y soporte experto de Odoo para optimizar sus operaciones.