Skip to content

Lowercase email#17775

Merged
etiennejouan merged 20 commits intotwentyhq:mainfrom
BOHEUS:lowercase-email
Feb 10, 2026
Merged

Lowercase email#17775
etiennejouan merged 20 commits intotwentyhq:mainfrom
BOHEUS:lowercase-email

Conversation

@BOHEUS
Copy link
Copy Markdown
Collaborator

@BOHEUS BOHEUS commented Feb 8, 2026

Fixes twentyhq/core-team-issues#120 #16976
Partially related to #17711

Frontend check surprisingly was one-liner covering both email input and import files

Migration script will be done in next commit

Copilot AI review requested due to automatic review settings February 8, 2026 07:54
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Feb 8, 2026

Greptile Overview

Greptile Summary

This PR enforces lowercase email addresses across the stack:

  • Frontend: emailSchema now applies .lowercase().
  • Server args validation: validateEmailsFieldOrThrow was updated to validate primaryEmail and additionalEmails via new helpers (validateEmailValueOrThrow, validateAdditionalEmailArrayOrThrow) and tests were added for rejecting uppercase emails.
  • GraphQL: introduces assertIsValidEmail using a lowercase email check.
  • Record CRUD filters: EMAILS filter schema now attempts to require lowercase for eq/neq.

Blocking issues to address before merge:

  • validateEmailsFieldOrThrow no longer rejects non-string primaryEmail values (regression vs prior behavior).
  • validateAdditionalEmailArrayOrThrow has type/logic holes (declares/returns string, can throw a raw TypeError for non-string array entries instead of a domain exception).
  • assertIsValidEmail error message uses a non-interpolated ${value} literal.
  • EMAILS filter schema drops .string() in favor of z.email(), which likely breaks Zod schema typing/behavior in this codebase.

Confidence Score: 2/5

  • This PR is not safe to merge until validation regressions and runtime error paths are fixed.

  • Multiple server-side validation changes introduce definite regressions: primaryEmail can now be non-string without throwing, additionalEmails validation can throw a raw TypeError for non-string array entries, and the GraphQL email assertion has a broken error message. The filter schema change also likely breaks runtime validation by dropping .string() in favor of z.email().

  • packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util.ts; packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts; packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-email.util.ts; packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/field-filters.zod-schema.ts

Important Files Changed

Filename Overview
packages/twenty-front/src/modules/object-record/record-field/ui/validation-schemas/emailSchema.ts Adds .lowercase() to the frontend email Zod schema to enforce lowercase emails; no immediate functional issues found in this file alone.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/tests/validate-emails-field-or-throw.util.spec.ts Adds tests for rejecting uppercase emails. Note: current implementation likely fails the existing 'primaryEmail is not a string' test due to relaxed validation logic.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts Introduces additionalEmails validator but has type/logic issues: can return string, passes unknown items into string-only validator causing possible TypeError instead of domain exception.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-email-value-or-throw.util.ts Adds email validator enforcing lowercase + email format; signature claims value: string but includes null guard, and will throw if called with non-string.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util.ts Switches emails validation to new helpers, but now non-string primaryEmail values are not rejected (regression vs prior validateTextFieldOrThrow behavior).
packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-email.util.ts Adds GraphQL email assertion enforcing lowercase, but error message uses non-interpolated ${value} string literal.
packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/field-filters.zod-schema.ts Updates EMAILS filter schema to require lowercase for eq/neq, but drops .string() which likely changes schema type unexpectedly and can break filter validation.

Sequence Diagram

sequenceDiagram
  participant FE as Frontend
  participant API as DataArgProcessor
  participant EmailsV as validateEmailsFieldOrThrow
  participant EmailV as validateEmailValueOrThrow
  participant AddArrV as validateAdditionalEmailArrayOrThrow
  participant GQL as WorkspaceQueryRunner
  participant Assert as assertIsValidEmail

  FE->>FE: emailSchema (z.email().lowercase())
  FE->>API: submit emails payload
  API->>EmailsV: validateEmailsFieldOrThrow(value, fieldName)
  EmailsV->>EmailV: validate primaryEmail (if string)
  EmailsV->>AddArrV: validate additionalEmails
  AddArrV->>EmailV: validate each additional email

  GQL->>Assert: assertIsValidEmail(value)
  Assert-->>GQL: throws WorkspaceQueryRunnerException if invalid

Loading

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +12 to +15
export const validateAdditionalEmailArrayOrThrow = (
value: unknown,
fieldName: string,
): string | string[] | null => {
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.

Return type doesn’t match data shape

validateAdditionalEmailArrayOrThrow is used to validate the additionalEmails subfield (which is typed as string[] | null in validateEmailsFieldOrThrow), but it currently declares it can return string | string[] | null and even returns a plain string when the input is a string (typeof value === 'string' branch). That means invalid shapes can slip through type-wise and the validator can silently accept a string for a field that should be an array. This should return string[] | null and reject non-arrays rather than accepting/returning a string.

Comment on lines +25 to +28
case 'primaryEmail':
validateTextFieldOrThrow(subFieldValue, `${fieldName}.${subField}`);
if (typeof subFieldValue === 'string' && subFieldValue) {
validateEmailValueOrThrow(subFieldValue, `${fieldName}.${subField}`);
}
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.

Non-string primaryEmail passes

In the primaryEmail branch, non-empty values that are not strings (e.g. primaryEmail: 12345) won’t throw because the code only validates when typeof subFieldValue === 'string' && subFieldValue. Previously this was rejected via validateTextFieldOrThrow. This breaks the existing test case expecting an exception for non-string primaryEmail.

Comment on lines +18 to +24
if (typeof value === 'string') {
validateEmailValueOrThrow(value, fieldName);
}

if (
!Array.isArray(value) ||
value.some((item) => !validateEmailValueOrThrow(item, fieldName))
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.

String input accepted for array field

This util is supposed to validate an array of additional emails, but it explicitly accepts typeof value === 'string' and only validates it (without throwing) before continuing. Since Array.isArray(value) will be false, it will still throw later, but the declared return type and this branch suggest strings are acceptable. More importantly, the error message thrown refers to “Array values need to be string” but the real issue is “value must be an array of strings”. Consider removing the string branch and making the thrown message match the actual constraint.

Comment on lines +22 to +30
if (
!Array.isArray(value) ||
value.some((item) => !validateEmailValueOrThrow(item, fieldName))
) {
const inspectedValue = inspect(value);

throw new CommonQueryRunnerException(
`Invalid value ${inspectedValue} for field "${fieldName} - Array values need to be string"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
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.

Runtime type hole in some()

value.some((item) => !validateEmailValueOrThrow(item, fieldName)) passes item (type unknown) into validateEmailValueOrThrow(value: string, ...). If the array contains non-strings, validateEmailValueOrThrow will attempt value.toLowerCase() and throw a TypeError rather than a CommonQueryRunnerException, changing error categorization and likely failing expected error handling. You need an explicit typeof item === 'string' check before calling the email validator.

Comment on lines +9 to +15
export const assertIsValidEmail = (value: string) => {
if (!z.email().lowercase().safeParse(value).success) {
throw new WorkspaceQueryRunnerException(
`Value "${value}" is not a valid email`,
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
{ userFriendlyMessage: msg`Invalid email format.` },
);
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.

Interpolation in error message

The exception message uses Value "${value}" is not a valid email inside a normal string literal, so it will literally include ${value} rather than the actual email. This makes debugging harder and breaks parity with other error messages that include the actual invalid value.

Comment on lines 353 to 363
primaryEmail: z
.object({
eq: z
.string()
.email()
.lowercase()
.optional()
.describe('Primary email equals'),
neq: z
.string()
.email()
.lowercase()
.optional()
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.

Email filter schema type regression

In the EMAILS filter schema, eq/neq were changed from z.string().email() to z.email().lowercase(). Dropping .string() likely changes the schema type/behavior (and may not exist depending on the Zod version in this repo), which can break filter parsing/validation at runtime. This should remain a string schema with .email().lowercase() chained on it.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to enforce/validate lowercase email handling across the frontend and backend to prevent case-related mismatches (notably impacting imports and filtering/querying by email).

Changes:

  • Added lowercase enforcement to email filter schemas and frontend email validation schema.
  • Introduced server-side utilities to validate email values (including lowercase constraint) and validate additional email arrays.
  • Expanded unit tests for validateEmailsFieldOrThrow to cover uppercase email inputs.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/field-filters.zod-schema.ts Enforces lowercase email values for eq/neq email filters.
packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-email.util.ts Adds a workspace query-runner helper to assert email validity with lowercase constraint.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util.ts Refactors emails-field validation to use new email-specific validators.
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-email-value-or-throw.util.ts Adds a dedicated validator for a single email value (incl. lowercase).
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts Adds validation for additionalEmails array contents (incl. lowercase).
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/tests/validate-emails-field-or-throw.util.spec.ts Adds test cases for uppercase primaryEmail and additionalEmails.
packages/twenty-front/src/modules/object-record/record-field/ui/validation-schemas/emailSchema.ts Enforces lowercase in frontend email schema.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +25
if (typeof value === 'string') {
validateEmailValueOrThrow(value, fieldName);
}

if (
!Array.isArray(value) ||
value.some((item) => !validateEmailValueOrThrow(item, fieldName))
) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

When additionalEmails is provided as a string, this function validates it but then continues and immediately throws because value is not an array. If string inputs are meant to be accepted (as validateArrayFieldOrThrow did), return early after validating the string; otherwise, drop the string branch and update the return type accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +20
export const validateEmailValueOrThrow = (
value: string,
fieldName: string,
): string | null => {
if (isNull(value)) return null;
if (
value !== value.toLowerCase() ||
!z.email().trim().safeParse(value).success
) {
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

validateEmailValueOrThrow is typed to accept value: string, but it contains null-handling and is invoked from codepaths where the input may not be a string (e.g. unknown JSON array items). As written, non-string inputs can cause runtime errors (e.g. value.toLowerCase is not a function) instead of a controlled CommonQueryRunnerException. Consider changing the signature to value: unknown and performing a typeof value === 'string' check up front (mirroring validateTextFieldOrThrow).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

3 issues found across 7 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts:18">
P2: String inputs are validated but not returned, so they fall through to the array check and always throw. This makes valid string inputs fail despite the function signature allowing `string`.</violation>

<violation number="2" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts:24">
P1: Missing type check before calling `validateEmailValueOrThrow`. If the array contains non-string items (e.g., numbers), calling `value.toLowerCase()` inside the validator will throw a `TypeError` rather than the expected `CommonQueryRunnerException`. Add a `typeof item === 'string'` check before calling the validator.</violation>
</file>

<file name="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-field-or-throw.util.ts:26">
P2: Non-string primaryEmail values now bypass validation. The new conditional only validates non-empty strings and no longer enforces that non-null values are strings, so numbers/booleans can slip through and be returned as `primaryEmail`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 8, 2026

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:13358

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-additional-email-array-or-throw.util.ts:24">
P2: Array validation now uses `forEach`, which always returns undefined, so the error block is never reached for arrays. Because `validateEmailValueOrThrow` returns null for null items (without throwing), arrays containing null values now pass validation even though only strings should be allowed.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
import { validateEmailValueOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-email-value-or-throw.util';

export const validateAdditionalEmailArrayOrThrow = (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shouldn't we rather convert to lowercase instead of throwing @BOHEUS ?

Copy link
Copy Markdown
Collaborator Author

@BOHEUS BOHEUS Feb 9, 2026

Choose a reason for hiding this comment

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

I'm not entirely sure about the approach whether it's better to throw an error to user or simply convert it, I just went with the former as I think it's better to inform user about an error and let them fix on their own rather than converting it to lowercase and having user surprised why email is changed

Speaking of converting to lowercase, there's already a convertion in transform-emails-value.util.ts

Let me know which approach is better and I'll make according changes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm lost, so is the lowercasing already in place? It seems so as you mentioned the transform-emails-value.util.ts

I don't think we should block honestly, we are normalizing the input which makes sense

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Basically:

  • lowercasing is already in place in transform-emails-value.util.ts (I missed it earlier focusing mainly on the issue and validation)
  • there's no validation whether email is lowercase or not (which can be redundant if there's already lowercasing in place as I'm thinking now but I still think it's better to inform user that they should change it on their own and remove lowercasing)
  • there's no validation whether email is really a email meaning if someone creates/updates a record via API with some kind of string instead of email, it'll be accepted instead of rejected (front has a validation so only backend is affected and it must be fixed)
  • lowercasing email has been implemented almost 3 months ago from what I can see (Common - Field validation #15491) so I'm not sure if earlier emails were lowercased by default or not, I'd assume they weren't

Given everything, should I remove lowercase validation (allowing emails to be lowercased by default) or remove default lowercasing? Email format validation must be added as well as migration command to run to ensure that all emails are really lowercase, please let me know which changes should I implement

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think I remember that a command to lowercase was actually run

So I would say, we can have:

  • keep the lowercasing we already have in place
  • no need for the command
  • let's use validation to block non valid emails => here I wonder if it should happen at a different / same place than the lowercasing. It feels weird to validate, then lowercase, I feel it's the same process @etiennejouan what do you think?

Copy link
Copy Markdown
Contributor

@etiennejouan etiennejouan Feb 10, 2026

Choose a reason for hiding this comment

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

I'm not sure we want to reject uppercase email. Data arg processor validate and transform each fields. Concerning EMAILS, it check the emails object format then it transforms with transformEmailsValue (lowercasing all emails - then no need for this PR's migration).

@BOHEUS when checking the issues you link to this PR, I think this one is relevant (the others should have been closed). We should have the same transformation on filter value to be sure condition matches. I'll include it in this sprint

Copy link
Copy Markdown
Collaborator Author

@BOHEUS BOHEUS Feb 10, 2026

Choose a reason for hiding this comment

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

Thanks @etiennejouan, should I apply changes proposed by Charles or do something more than that?

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.

I think that changes proposed by Charles are already live. I'll fix filtering issues on email. Then, I'm sorry but I think we should close this PR. Do not hesitate to reach me it seems unclear.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts">

<violation number="1" location="packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/constants/failing-create-input-by-field-metadata-type.constant.ts:350">
P2: `additionalEmailsField` is not defined in the test object metadata; these failing EMAILS cases target a non-existent field and will not validate email formatting.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@etiennejouan etiennejouan left a comment

Choose a reason for hiding this comment

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

Thanks @BOHEUS nice PR and good catch ! 🙏

I comment the change I made, feel free to reach me if not clear.

import { validateEmailValueOrThrow } from 'src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-email-value-or-throw.util';

export const validateAdditionalEmailArrayOrThrow = (
export const validateEmailsAdditionalEmailsSubfieldOrThrow = (
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.

nitpick : renamed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

While renaming, you probably duplicated a file by accident, I deleted it


exports[`Create input validation - EMAILS Gql create input - failure EMAILS - should fail with : {"emailsField":"not-an-email"} 1`] = `"Expected type "EmailsCreateInput" to be an object."`;

exports[`Create input validation - EMAILS Gql create input - failure EMAILS - should fail with : {"emailsField":{"additionalEmails":"not-an-email"}} 1`] = `"Invalid string value 'not-an-email' for email field "emailsField.additionalEmails""`;
Copy link
Copy Markdown
Contributor

@etiennejouan etiennejouan Feb 10, 2026

Choose a reason for hiding this comment

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

when updating integration test, you can launch this command to be sure test is ok + to update snapshot if needed

Image

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Could you please write down a command I can run to check if test is OK? I don't have such dropdown in my IDE

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.

nx run twenty-server:jest --config ./jest-integration.config.ts packages/twenty-server/test/integration/graphql/suites/inputs-validation/create-validation/emails-field-create-input-validation.integration-spec.ts --silent=false --updateSnapshot

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Thanks!

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-additional-emails-subfield-or-throw.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-additional-emails-subfield-or-throw.util.ts:34">
P2: userFriendlyMessage repeats inspected technical details; per guidance it should stay generic and avoid duplicating the internal error.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

throw new CommonQueryRunnerException(
`Invalid value ${inspectedValue} for field "${fieldName} - Array values need to be string"`,
CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
{ userFriendlyMessage: msg`Invalid value: "${inspectedValue}"` },
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

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

P2: userFriendlyMessage repeats inspected technical details; per guidance it should stay generic and avoid duplicating the internal error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-additional-emails-subfield-or-throw.util.ts, line 34:

<comment>userFriendlyMessage repeats inspected technical details; per guidance it should stay generic and avoid duplicating the internal error.</comment>

<file context>
@@ -0,0 +1,39 @@
+    throw new CommonQueryRunnerException(
+      `Invalid value ${inspectedValue} for field "${fieldName} - Array values need to be string"`,
+      CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA,
+      { userFriendlyMessage: msg`Invalid value: "${inspectedValue}"` },
+    );
+  }
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-primary-email-subfield-or-throw.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-primary-email-subfield-or-throw.util.ts:16">
P2: Non-string inputs now return null instead of throwing, making the explicit type check unreachable and weakening validation.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

if (
!z.email({ pattern: z.regexes.unicodeEmail }).safeParse(value).success &&
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The code uses z.regexes.unicodeEmail, but z.regexes is not a valid property in the Zod v4 API, which will cause a runtime TypeError during validation.
Severity: CRITICAL

Suggested Fix

Remove the { pattern: z.regexes.unicodeEmail } argument from the z.email() call. The default email validation provided by Zod is generally sufficient. If a specific unicode-aware regex is required, it should be defined locally and passed as the pattern.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-emails-primary-email-subfield-or-throw.util.ts#L29

Potential issue: The validation logic on line 29 calls `z.email({ pattern:
z.regexes.unicodeEmail })`. However, the `z.regexes` object does not exist in the
version of Zod being used (v4.1.11). This will result in a `TypeError` at runtime when
the validation function is invoked, as it attempts to access the `unicodeEmail` property
of an `undefined` value. This error will occur whenever email validation is performed on
email fields during data mutations, causing the server request to fail.

@etiennejouan etiennejouan added this pull request to the merge queue Feb 10, 2026
Merged via the queue into twentyhq:main with commit 16d4145 Feb 10, 2026
58 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

Fails
🚫

node failed.

Log

Details
�[31mError: �[39m SyntaxError: Unexpected token 'C', "Contributo"... is not valid JSON
    at JSON.parse (<anonymous>)
�[90m    at parseJSONFromBytes (node:internal/deps/undici/undici:4259:19)�[39m
�[90m    at successSteps (node:internal/deps/undici/undici:6882:27)�[39m
�[90m    at readAllBytes (node:internal/deps/undici/undici:5807:13)�[39m
�[90m    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)�[39m
danger-results://tmp/danger-results-7bc4b969.json

Generated by 🚫 dangerJS against 35be69a

@twenty-eng-sync
Copy link
Copy Markdown

Hey @etiennejouan! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

1 similar comment
@twenty-eng-sync
Copy link
Copy Markdown

Hey @etiennejouan! After you've done the QA of your Pull Request, you can mark it as done here. Thank you!

@BOHEUS BOHEUS deleted the lowercase-email branch February 10, 2026 19:31
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.

Email field should always be lowercase

4 participants