From ff7aed19711227f01b130a40a29d68faacf82121 Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Wed, 4 Mar 2026 10:14:47 +0000 Subject: [PATCH] DELETE person fails with misleading "Data validation error" masking a PostgreSQL deadlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://sonarly.com/issue/3840?type=bug A PostgreSQL deadlock during a DELETE on the person table is caught by an overly broad error handler that replaces all PostgreSQL error messages with "Data validation error.", returning a misleading HTTP 500 to the API consumer. Fix: The catch-all in `computeTwentyORMException` replaced every unhandled PostgreSQL error message with the hardcoded string `"Data validation error."`, including transient concurrency errors like deadlocks (`40P01`) and serialization failures (`40001`). This made it impossible for API consumers to distinguish a permanent validation failure from a transient error that would succeed on retry. The fix adds an explicit guard **before** the catch-all that intercepts `DEADLOCK_DETECTED` and `SERIALIZATION_FAILURE` and returns a `TwentyORMException` (instead of a `PostgresException`) with: - The real `error.message` (e.g., `"deadlock detected"`) — preserving debuggability - `TwentyORMExceptionCode.QUERY_READ_TIMEOUT` — the closest existing code for "transient, please retry" - A user-friendly message: `"We are experiencing a temporary issue with our database. Please try again later."` — consistent with the existing `QUERY_READ_TIMEOUT` pattern Using `TwentyORMException` also means the error is caught by `workspaceQueryRunnerRestApiExceptionHandler` rather than falling through to `HttpExceptionHandlerService`, which was the path that produced the misleading `InternalServerErrorException: Data validation error.`. ```typescript file=packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts lines=61-72 if ( errorCode === POSTGRESQL_ERROR_CODES.DEADLOCK_DETECTED || errorCode === POSTGRESQL_ERROR_CODES.SERIALIZATION_FAILURE ) { return new TwentyORMException( error.message, TwentyORMExceptionCode.QUERY_READ_TIMEOUT, { userFriendlyMessage: msg`We are experiencing a temporary issue with our database. Please try again later.`, }, ); } ``` --- .../error-handling/compute-twenty-orm-exception.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts index f9fad80e6b1..f7ef9e37c5a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts @@ -58,6 +58,19 @@ export const computeTwentyORMException = async ( ); } + if ( + errorCode === POSTGRESQL_ERROR_CODES.DEADLOCK_DETECTED || + errorCode === POSTGRESQL_ERROR_CODES.SERIALIZATION_FAILURE + ) { + return new TwentyORMException( + error.message, + TwentyORMExceptionCode.QUERY_READ_TIMEOUT, + { + userFriendlyMessage: msg`We are experiencing a temporary issue with our database. Please try again later.`, + }, + ); + } + if ( isDefined(errorCode) && Object.values(POSTGRESQL_ERROR_CODES).includes(errorCode)