From 70e22ce10b85707017fd72744ccfefe330f87a92 Mon Sep 17 00:00:00 2001 From: Sonarly Claude Code Date: Mon, 6 Apr 2026 18:25:37 +0000 Subject: [PATCH] chore: improve monitoring for fix: handle stale API key role cache gracefully in **Monitoring fix in `permission-graphql-api-exception-handler.util.ts`:** Moved `API_KEY_ROLE_NOT_FOUND` and `APPLICATION_ROLE_NOT_FOUND` from the raw re-throw group (line 74) to a new `ForbiddenError` mapping. This ensures these exceptions are converted to proper GraphQL errors before reaching the client, matching the pattern used for similar auth/permission errors (e.g., `PERMISSION_DENIED`, `NO_AUTHENTICATION_CONTEXT`). Previously, these two codes fell through to `throw error` which propagated the raw `PermissionsException` up the stack, causing Sentry to capture it as an unhandled error. Now they're wrapped as `ForbiddenError` GraphQL responses, which: 1. Return a clean, structured error to the API consumer 2. Prevent unnecessary Sentry noise since `ForbiddenError` is a handled GraphQL error type 3. Are consistent with how `API_KEY_NO_ROLE_ASSIGNED` is handled in `api-key-graphql-api-exception-handler.util.ts` --- .../utils/permission-graphql-api-exception-handler.util.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts index 98659d9a86b..e60bb23b8e4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts @@ -51,6 +51,9 @@ export const permissionGraphqlApiExceptionHandler = ( case PermissionsExceptionCode.FIELD_PERMISSION_NOT_FOUND: case PermissionsExceptionCode.PERMISSION_NOT_FOUND: throw new NotFoundError(error); + case PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND: + case PermissionsExceptionCode.APPLICATION_ROLE_NOT_FOUND: + throw new ForbiddenError(error); case PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED: case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND: case PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH: @@ -66,11 +69,9 @@ export const permissionGraphqlApiExceptionHandler = ( case PermissionsExceptionCode.METHOD_NOT_ALLOWED: case PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED: case PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND: - case PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND: case PermissionsExceptionCode.JOIN_COLUMN_NAME_REQUIRED: case PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND: case PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND: - case PermissionsExceptionCode.APPLICATION_ROLE_NOT_FOUND: throw error; default: { return assertUnreachable(error.code);