Skip to content

Odoo MissingError: Record Does Not Exist or Has Been Deleted

DeployMonkey Team · March 22, 2026 10 min read

The Error

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

# Or in the UI:
"The record you are trying to access has been deleted."

What It Means

Code is trying to access a record (by its ID) that no longer exists in the database. The record was either deleted by another user, a cron job, a cascade delete, or the ID reference is stale.

Cause 1: Record Deleted by Another User

# User A opens sale order #42
# User B deletes sale order #42
# User A clicks a button → MissingError

# Fix: handle gracefully in code
record = self.env['sale.order'].browse(42)
if not record.exists():
    raise UserError("This record has been deleted.")
record.action_confirm()

Cause 2: Cascade Delete

# Deleting a parent record cascades to children:
# Delete partner → deletes their sale orders → deletes order lines
# If code holds a reference to an order line → MissingError

# Check ondelete behavior:
partner_id = fields.Many2one('res.partner', ondelete='cascade')
# cascade: delete child when parent is deleted
# set null: set to NULL when parent is deleted
# restrict: prevent parent deletion if children exist

# Fix: use ondelete='restrict' to prevent accidental cascades
# Or check .exists() before accessing related records

Cause 3: Cron Job Cleanup

# A cron deletes old records while a user is viewing them:
# - Cron cleans up old draft quotations
# - User has an old draft open in their browser
# - User clicks Save → MissingError

# Fix: cron should check for active sessions
# Or: soft-delete (active=False) instead of hard delete

Cause 4: Stale Browser Tab

# User has Odoo open in a browser tab for hours
# Records were deleted in that time
# User tries to interact → MissingError

# Fix (frontend): catch the error and show a friendly message
# Fix (backend): always check .exists() before operations

Cause 5: Orphaned Many2many References

# Many2many relation table has IDs of deleted records:
SELECT * FROM model_a_model_b_rel
WHERE model_a_id NOT IN (SELECT id FROM model_a)
   OR model_b_id NOT IN (SELECT id FROM model_b);

# Fix: clean up orphaned references
DELETE FROM model_a_model_b_rel
WHERE model_a_id NOT IN (SELECT id FROM model_a)
   OR model_b_id NOT IN (SELECT id FROM model_b);

Cause 6: Database Restore Mismatch

# Restored an older backup but filestore is newer
# Or: restored database but ir_attachment references non-existent records

# Fix: restore both database AND filestore from the same backup point

Prevention Patterns

# Pattern 1: Always check exists()
def action_process(self):
    for record in self:
        if not record.exists():
            continue
        record._do_processing()

# Pattern 2: Use filtered()
records = self.env['sale.order'].browse(ids)
existing = records.filtered(lambda r: r.exists())
# Or simply:
existing = records.exists()  # Returns recordset of existing records

# Pattern 3: Catch MissingError
from odoo.exceptions import MissingError
try:
    record.action_confirm()
except MissingError:
    raise UserError("This record was deleted. Please refresh.")

# Pattern 4: Use sudo_browse_or_404 pattern
record = self.env['sale.order'].browse(record_id)
if not record.exists():
    return request.not_found()  # In controllers

Diagnosis Steps

  1. Note the model and ID from the error (e.g., sale.order(42))
  2. Check if the record exists: SELECT id FROM sale_order WHERE id = 42;
  3. If it doesn't exist, check who deleted it: audit log or git blame on cron code
  4. If it exists, check if there's a permissions issue instead
  5. Check for cascade deletes on related models

DeployMonkey

DeployMonkey's AI agent catches MissingError patterns and identifies the root cause — whether it is a cascade delete, cron cleanup, or orphaned reference. It suggests the fix and helps prevent recurrence.