## What & why
Two related fixes that clean up Sentry reporting for the AI chat flow.
### 1. Enriched zero-output stream captures
The AI chat stream's rejection handler previously skipped only
`AbortError` and captured everything else to Sentry as-is. Two problems:
- The SDK's bare `NoOutputGeneratedError` carries no troubleshooting
context, so the Sentry issues were unactionable (no model, provider,
workspace, or conversation size).
- Expected interruptions (user abort, `STREAM_INTERRUPTED`) still
generated noise.
The rejection handler now handles three cases inline:
- `AbortError` and `STREAM_INTERRUPTED` are expected interruptions and
are not captured.
- `NoOutputGeneratedError` is replaced with a single error whose message
carries the full context as plain JSON: model, provider, workspace,
thread, stream, turn, message count, conversation size, elapsed time,
and the underlying stream error - recorded via a new `onError` handler,
which also keeps stream-level errors visible in the worker logs.
- Anything else is captured unchanged.
The stable message prefix and single capture site keep zero-output
events grouped separately from raw provider errors in Sentry.
### 2. Keep client-error domain exceptions out of Sentry
`BILLING_CREDITS_EXHAUSTED` (a 402, i.e. an expected "user out of
credits" condition) was landing in Sentry. Root cause: `CustomException`
carries no HTTP status, so the worker/BullMQ path hands the raw
exception to `shouldCaptureException`, which can't tell a 4xx client
error from a 5xx server error and captures everything. The GraphQL/REST
edges convert exceptions first, but background jobs bypass those
converters.
Fix, mirroring how `HttpException.getStatus()` already works:
- `CustomException` gains an intrinsic `statusCode`.
- `shouldCaptureException` skips a `CustomException` whose `statusCode <
500`, as a branch symmetric to the existing `HttpException` check. This
covers every path, including the worker.
- `BillingException` populates `statusCode` from the existing
`getBillingExceptionStatusCode` mapping, so credits-exhausted (402)
stays out of Sentry while the 500-mapped billing codes are still
captured.
Exceptions that don't set `statusCode` default to undefined and are
captured exactly as before, so other domains are unaffected until they
opt in.
## Tests
- ai-chat unit suite passes (13 suites, 76 tests).
- Existing billing exception handler tests pass.
- `typecheck` passes.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23426?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
## What
Return HTTP 422 instead of 500 for `LOGIC_FUNCTION_EXECUTION_ERROR` in
the route trigger exception filter.
## Why
When a logic function's user code fails (e.g. an HTTP call inside the
function returns a 502 from an upstream service), the exception was
mapped to HTTP 500. This caused two problems:
- **Sentry noise**: `shouldCaptureException` captures all 5xx responses,
so every user-code failure was reported as a platform error. This
generated ~56k Sentry events over 2 months for a single workspace's
logic function hitting a transient upstream 502.
- **Webhook retry loops**: Webhook senders like GitHub auto-retry on 5xx
responses, amplifying the event count.
`LOGIC_FUNCTION_EXECUTION_ERROR` is a user-code error, not a platform
error. A 422 (Unprocessable Entity) correctly signals that the request
could not be processed due to the logic function's own failure, without
triggering Sentry capture or webhook retries.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21656?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
Resolves [Code Scanning Alert
180](https://github.com/twentyhq/twenty/security/code-scanning/180).
- Normalize unexpected GraphQL errors in convertExceptionToGraphql to a
generic "Internal Server Error" instead of exposing exception.name
directly to clients.
- Only attach stack and response (original error message) in
development, so production responses don’t leak internal class names,
implementation details, or stack traces, while observability is
preserved via `ExceptionHandlerService`/Sentry.
- Keep behavior consistent with `convertHttpExceptionToGraphql`, which
also only exposes detailed response and stack information when `NODE_ENV
=== DEVELOPMENT`.
RestApiExceptionFilter is used as an exception filter for the core
controller which is used for crud operations on our objects (equivalent
of our dynamic queries findManyPeople etc. on the graphql API).
Exceptions were leading a 400 / BadRequestException response status
which can be confusing to users.
By default we should actually throw a 500 if the error was not handled
priorily, but we have not implemented input validation for the REST api
so we fear to be flooded with errors that should not be 500 but 400 due
to user inputs. A solution should be brought [with this
ticket](https://github.com/twentyhq/core-team-issues/issues/1027) but it
has not been prioritized yet.
Follow-up on https://github.com/twentyhq/twenty/pull/12007
In this PR
- adding a filter on HttpExceptionHandlerService to filter out 4xx
errors from driver handling (as we do for graphQL errors: see
useGraphQLErrorHandler hook - only filteredIssues are sent to`
exceptionHandlerService.captureExceptions()`.)
- grouping together more missing metadata issues
- attempting to use error codes as issues names in sentry to improve UI;
for now it says "Error" all the time
# This PR
- Addressing #3644
- Migrates the `DELETE /rest/*` endpoint to use TwentyORM
- Factorizes common middleware logic into a common module
---------
Co-authored-by: martmull <martmull@hotmail.fr>
- [ ] Catch this specific `500` error
- [ ] Make sure catched `500` errors are sent to sentry for the Cloud
version
- [ ] Hide the option to sync email with google in this situation where
the according env var is missing
- [x] Add Worskpace information to all catched errors for better
debugging
fix#8607
We call convertExceptionToGraphQLError in the exception handler for http
exceptions but we don't take into account those that already are
graphqlErrors and because of that the logic of convertExceptionToGraphql
is to fallback to a 500.
Now if the exception is a BaseGraphqlError (custom graphql error we
throw in the code), we throw them directly.
BEFORE
<img width="957" alt="Screenshot 2024-07-12 at 15 33 03"
src="https://github.com/user-attachments/assets/22ddae13-4996-4ad3-8f86-dd17c2922ca8">
AFTER
<img width="923" alt="Screenshot 2024-07-12 at 15 32 01"
src="https://github.com/user-attachments/assets/d3d6db93-6d28-495c-a4b4-ba4e47d45abd">
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Our exception handler has to filter out some errors/exceptions so they
are not caught by the ExceptionHandlerDriver (Logged or Sentry for
example). This is done for Http errors in the range of 4xx and also
makes sure they are converted back to Graphql validation errors.
However, graphql validation errors that are already managed by Yoga
(with Schema validation) should also be filtered out, this PR should fix
that behaviour
Query read timeouts happen when a remote server is not available. It
breaks:
- the remote server show page
- the record table page of imported remote tables
This PR will catch the exception so it does not go to Sentry in both
cases.
Also did 2 renaming.
## Context
We have a non-nullable constraint on authorId in attachments and
documents, until we have soft-deletion we need to handle deletion of
workspace-members and their attachments/documents.
This PR introduces pre-hooks to deleteOne/deleteMany
This is called when a user deletes a workspace-member from the members
page
Next: needs to be done on user level as well. This is called when users
try to delete their own accounts. I've seen other issues such as
re-creating a user with a previously used email failing.