ErrLookup › Background articles › UserError: Odoo's user-facing guard exception, and its Supabase and n8n variants
UserError: Odoo's user-facing guard exception, and its Supabase and n8n variants
UserError is a deliberately thrown, user-facing exception that means an application-level guard rejected the operation: a business rule, compliance lock, registration conflict, or input validation fired on purpose, and it is not a bug or a crash. Developers meet it most often in Odoo (odoo.exceptions.UserError, raised by constraints, wizards, and Peppol/IAP proxy connectors), and as a small ApplicationError subclass in Supabase edge functions and n8n workflow code, where it surfaces as a translated dialog message, an HTTP 400 with a structured body, or a CLI error.
Distilled from 307 documented records across 3 repositories.
Background
UserError is produced at the application layer and thrown on purpose. It is not a crash: guard code decides an operation must not proceed and raises this class so the caller receives an explainable rejection instead of silent data damage. In Odoo it is the standard user-facing exception (odoo.exceptions.UserError), raised from constraint checks, ondelete guards, wizard computes, write() preconditions, and Peppol/IAP proxy connector handlers. In Supabase's search-embeddings edge function, UserError extends ApplicationError/Error and an outer catch converts it into an HTTP 400 with a structured JSON body carrying the OpenAI moderation categories. In n8n it appears in the CLI import path for ownership checks and in node configuration validation, where one instance is immediately caught and rethrown as a NodeOperationError that carries the original text as its description.
The family exists to protect invariants the system refuses to violate silently. The Odoo records guard legal immutability (hashed journal entries and restricted audit trails that compliance regimes make irreversible), anti-phishing locks on trusted bank accounts, one-registration-per-participant and one-connection-per-service rules on the Peppol network, and the consistency of payments and reversals across companies and journal types. The Supabase record enforces content policy by submitting the query to OpenAI's Moderation API before generating an embedding; the n8n records enforce ownership integrity on workflow import and valid date configuration. Messages are written for end users - translated where the framework supports it, and frequently interpolating the offending fields, entries, or upstream error codes - because the intended remedy is human action, not automatic recovery.
From the caller's side the shape varies by library. An Odoo client sees the transaction rolled back and a message dialog or RPC error string, for example a list of the exact fields and hashed entry names it tried to modify. A Supabase caller receives a 400 response whose body carries the moderation categories that tripped the flag. An n8n CLI user gets the error printed at import time, while in the Chat Trigger path the UserError itself is re-wrapped, so the class a caller observes can differ from the class that was thrown. The exact presentation is therefore library-specific and even call-path-specific.
One split matters for diagnosis: most UserErrors are deterministic guards - retrying the identical input fails identically, whether it is a moderation flag, a format check, or an immutability lock. But several Odoo Peppol records wrap transport-level failures (DNS failure, connection refused, TLS errors, a 10-second timeout, non-JSON responses) or structured upstream error payloads into UserError as well. The class name alone does not say whether a condition is permanent; the interpolated message and any error code must be read first.
Common causes
- Registration and ownership conflicts.An identifier is already claimed or no longer yours. A Peppol participant is already registered on the network, an EDI proxy user already exists for the identification, a restored database's proxy token is out of sync with the server, or an imported workflow ID is already owned by another project. These guards prevent two owners for one registration.
- Upstream service failures surfaced as UserError.Odoo's Peppol connector raises UserError for transport failures (unreachable proxy, DNS problems, 10-second timeout, non-JSON response) and for structured error payloads returned by the proxy; Supabase's edge function raises it when OpenAI's Moderation API flags the query. Remote trouble is converted into a local, user-readable error.
- Missing configuration or data preconditions.The operation requires setup that is absent: a validated partner bank account for bank-requiring payment methods, a bound invoice PDF report, a verified Peppol endpoint on the partner, an implemented geolocation provider method, or an empty proxy can_connect response because the earlier fetch failed.
- Editing or deleting immutable records.Legally protected data refuses writes and deletes: hashed journal entries freeze their hashed fields, restricted audit-trail messages cannot be removed or edited, partners on hashed entries cannot be merged, and trusted bank accounts lock their number and owner. These guards are irreversible by design.
- Invalid input format or unsupported values.A date string the Date constructor cannot parse, a Peppol endpoint failing the EAS scheme's format check, or a search domain using an operator the domain parser does not implement. The guard fires before the operation starts and names the offending value or expected format.
- Cross-company or mismatched-context operations.Registering one payment for entries under different company roots, changing a tax's company while journal items in other companies reference it, or reversing entries in a journal whose type does not match the original moves.
- Missing localization or module dependency.A journal's invoice-reference scheme or a government cancellation flow needs a localization module (for example l10n_be or l10n_mx_edi) that is not installed, or a master chart template (syscoheda, syscebnl) was selected where a country-specific template is required.
What usually fixes it
- Read the interpolated message before anything else. UserErrors are built to be actionable: they name the offending fields, hashed entries, expected formats, or proxy error codes, and several records point to a specific settings screen as the remedy.
- Distinguish permanent guards from transient wraps. Most UserErrors are deterministic - an identical retry fails identically (moderation flags, format checks, immutability locks), so fix the precondition instead. Transport-wrapped Peppol failures can be retried once connectivity to the proxy is restored.
- Pre-validate state before invoking the guarded operation. Check flags such as need_cancel_request, peppol_verification_state, and is_token_out_of_sync, and filter inputs (one company root per payment, one account type per change-period run, identifier normalized to the EAS format) so the guard never fires.
- Work with immutability, not against it. Post reversals or correcting entries instead of editing hashed moves, archive instead of delete or merge, and create a new bank-account record instead of repointing a trusted one.
- Keep automation guard-aware. Filter crons to active records, group data by company root before batch operations, strip immutable fields from import payloads, and skip locked records with a logged warning instead of letting the batch crash.
- Surface the error's metadata to the user. The exception often carries structured data - moderation categories, expected-format examples, proxy error codes - intended for an end user; show it rather than swallowing it into a generic failure.
Go deeper
- Connection failures: ECONNREFUSED, ECONNRESET, and friends — why connections get refused, reset, or dropped.
- DNS resolution errors: ENOTFOUND and getaddrinfo failures — how hostname lookups fail and how to debug them.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Documented occurrences
- Flagged content(supabase/supabase)
- Peppol Error [code=%(error_code)s]: %(error_subject)s %(error_message)s(odoo/odoo)
- Failed to connect to Peppol Access Point. This might happen if you restored a database from a backup or copied it without neutralization. To fix this, please go to Settings > Accounting > Peppol Settings and click on 'Reconnect this database'.(odoo/odoo)
- To record payments with %(payment_method)s, the recipient bank account must be manually validated. You should go on the partner bank account in order to validate it.(odoo/odoo)
- Could not connect to Proxy Server.(odoo/odoo)
- Journal should be the same type as the reversed entry.(odoo/odoo)
- A connection to '%s' already exists.(odoo/odoo)
- The combination of reference model and reference type on the journal is not implemented(odoo/odoo)
- You cannot remove parts of a restricted audit trail. Archive the record instead.(odoo/odoo)
- There is no template that applies to invoices.(odoo/odoo)
- Peppol Error [code=%(error_code)s]: %(error_subject)s %(error_message)s(odoo/odoo)
- Partners that are used in hashed entries cannot be merged.(odoo/odoo)
- You cannot edit the following fields: %(fields)s. The following entries are already hashed: %(entries)s(odoo/odoo)
- Unsupported domain condition {condition!r}(odoo/odoo)
- You can only request a cancellation for invoice sent to the government.(odoo/odoo)
- Invalid date format(n8n-io/n8n)
- The credential with ID "${workflow.id}" is already owned by ${currentOwner}. It can't be re-owned by ${newOwner}.(n8n-io/n8n)
- You can't change the company of your tax since there are some journal items linked to it.(odoo/odoo)
- The %s chart template shouldn't be selected directly. Instead, you should directly select the chart template related to your country.(odoo/odoo)
- Your identifier does not have a valid format.%s(odoo/odoo)
…and 287 more across the corpus — use search.
Honest provenance: generated on 2026-08-15 from AI-assisted analysis of the linked records. See how records are made.