Skip to content

fix: surface proper errors for People create/delete constraint violations#21270

Open
Pantkartik wants to merge 3 commits into
twentyhq:mainfrom
Pantkartik:fix/people-records-data-validation-error
Open

fix: surface proper errors for People create/delete constraint violations#21270
Pantkartik wants to merge 3 commits into
twentyhq:mainfrom
Pantkartik:fix/people-records-data-validation-error

Conversation

@Pantkartik

Copy link
Copy Markdown
Contributor

Summary

Fixes #21119createPerson / deletePerson mutations fail with a generic INTERNAL_SERVER_ERROR: "Data validation error." instead of a meaningful error, blocking core CRM record management.

Root Cause

The computeTwentyORMException function in twenty-orm contains a catch-all block that matches every known Postgres error code via Object.values(POSTGRESQL_ERROR_CODES).includes(errorCode) and discards all error detail, throwing:

throw new PostgresException('Data validation error.', errorCode);

This PostgresException is then converted by the GraphQL error handler into INTERNAL_SERVER_ERROR, masking the real constraint violation. Common mutations like createPerson that hit:

  • NOT_NULL_VIOLATION (23502) — a required field is missing
  • FOREIGN_KEY_VIOLATION (23503) — referenced record missing or deletion blocked by a FK
  • RESTRICT_VIOLATION (23001) — record deletion blocked by a referencing row
  • CHECK_VIOLATION (23514) — field value fails a DB check constraint

...all silently surface as the same opaque "Data validation error." with INTERNAL_SERVER_ERROR.

Already handled correctly before the catch-all:

  • UNIQUE_VIOLATION → delegates to handleDuplicateKeyError
  • INVALID_TEXT_REPRESENTATIONTwentyORMException(INVALID_INPUT)
  • Query read timeout → TwentyORMException(QUERY_READ_TIMEOUT)

Fix

Add explicit handling before the catch-all for the four most common data-integrity constraint errors, converting them to TwentyORMException(INVALID_INPUT) with a clear user-facing message. The GraphQL error handler then returns BAD_USER_INPUT (400) instead of INTERNAL_SERVER_ERROR (500).

Changes

packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts

Added specific handling for:

Postgres Code Constant User-facing message
23502 NOT_NULL_VIOLATION "A required field is missing. Please provide all required values and try again."
23503 FOREIGN_KEY_VIOLATION "This operation references a record that does not exist or cannot be modified due to existing relationships."
23001 RESTRICT_VIOLATION "This record cannot be deleted because it is still referenced by other records."
23514 CHECK_VIOLATION "One or more field values are invalid. Please check your input and try again."

Before / After

Before:

{
  "data": { "createPerson": null },
  "errors": [{
    "message": "Data validation error.",
    "extensions": { "code": "INTERNAL_SERVER_ERROR" }
  }]
}

After (e.g. NOT_NULL_VIOLATION):

{
  "data": { "createPerson": null },
  "errors": [{
    "message": "A required field is missing. Please provide all required values and try again.",
    "extensions": { "code": "BAD_USER_INPUT" }
  }]
}

@twenty-ci-bot-public

Copy link
Copy Markdown

👋 Thanks for contributing to Twenty!

Your PR has been set to draft while you work on it. Once you're done, mark it as Ready for review and our automated checks will run.

Looking forward to your contribution!

…ions

When createPerson or deletePerson mutations hit Postgres constraint errors
(NOT_NULL_VIOLATION, FOREIGN_KEY_VIOLATION, RESTRICT_VIOLATION,
CHECK_VIOLATION), the catch-all in computeTwentyORMException was throwing a
generic PostgresException('Data validation error.') which the GraphQL layer
then returned as INTERNAL_SERVER_ERROR — masking the real cause entirely.

Root cause: the catch-all block at the bottom of computeTwentyORMException
matches every known Postgres error code (via Object.values(POSTGRESQL_ERROR_CODES).includes())
and discards the original error details, replacing them with the opaque
'Data validation error.' message.

Fix: add specific handling for common data-integrity constraint errors before
the catch-all, converting them to TwentyORMException(INVALID_INPUT) with a
user-friendly message. This surfaces the real reason for the failure to the
client and allows the GraphQL error handler to return BAD_USER_INPUT instead
of INTERNAL_SERVER_ERROR.

Errors now handled explicitly:
- NOT_NULL_VIOLATION (23502): required field missing
- FOREIGN_KEY_VIOLATION (23503): referenced record doesn't exist / can't delete
- RESTRICT_VIOLATION (23001): deletion blocked by dependent records
- CHECK_VIOLATION (23514): field value fails a database check constraint

Fixes twentyhq#21119
@Pantkartik Pantkartik force-pushed the fix/people-records-data-validation-error branch from 3cdd213 to a74e0c5 Compare June 5, 2026 21:54
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jun 5, 2026

Copy link
Copy Markdown

🔍 Automated Pre-Review

No issues detected - This PR is ready for human review.


🧭 External PR Quality Review

🟠 Needs triage for the following reason(s):

  • Possible duplicate / the linked issue already has other candidate PRs

cc @prastoin

Checks

  • CI: pending
  • 🔗 Linked issue: Fixes #21119
  • 👥 Duplicate: possible

Detailed findings (duplicate candidates, standards notes, summary) are in the workflow run logs.


View details

Automated pre-review — human approval still required.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

…ng comments

Replaces 4 structurally identical if-blocks with a single
CONSTRAINT_VIOLATION_MESSAGES map lookup. Adding future constraint
handlers is now a one-line map entry.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't create or delete People records

2 participants