{"record":{"id":"0cf71b8afbaf690b","repo":"twentyhq/twenty","slug":"createperson-did-not-return-an-id","errorCode":null,"errorMessage":"createPerson did not return an id","messagePattern":"createPerson did not return an id","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/twenty-apps/internal/twenty-partners/src/modules/opportunity/intake/services/import-opportunity-from-tft.service.ts","lineNumber":75,"sourceCode":"  const lastName = isNonEmptyString(pointOfContact?.lastName)\n    ? pointOfContact.lastName.trim()\n    : '';\n  if (email === undefined && firstName === '' && lastName === '') return undefined;\n\n  if (email !== undefined) {\n    const existing = await findPersonIdByPrimaryEmail(client, email);\n    if (existing !== undefined) return existing;\n  }\n\n  const personData: CoreSchema.PersonCreateInput = { name: { firstName, lastName } };\n  if (email !== undefined) personData.emails = { primaryEmail: email };\n  if (companyId !== undefined) personData.companyId = companyId;\n\n  const result = await client.mutation({\n    createPerson: { __args: { data: personData }, id: true },\n  });\n  const id = result.createPerson?.id;\n  if (id === undefined) throw new Error('createPerson did not return an id');\n  return id;\n}\n\n// Manual one-way copy of one Opportunity from the TFT workspace into partners; idempotent on\n// tftOpportunityId (name as a fallback for manual calls).\nexport async function importOpportunityFromTft(\n  input: ImportOpportunityFromTftInput,\n): Promise<ImportOpportunityFromTftResult> {\n  try {\n    const client = new CoreApiClient();\n    const name = input.name.trim();\n    const tftOpportunityId = isNonEmptyString(input.tftOpportunityId)\n      ? input.tftOpportunityId.trim()\n      : undefined;\n\n    const dedupeFilter: CoreSchema.OpportunityFilterInput =\n      tftOpportunityId !== undefined\n        ? { tftOpportunityId: { eq: tftOpportunityId } }","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/twentyhq/twenty/blob/1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6/packages/twenty-apps/internal/twenty-partners/src/modules/opportunity/intake/services/import-opportunity-from-tft.service.ts#L57-L93","documentation":"findOrCreatePersonId in import-opportunity-from-tft guards the createPerson mutation result the same way as the company guard. After selecting createPerson.id, an undefined id means the server returned a null person payload rather than throwing. It exists because a Person insert can be silently rejected (e.g. email uniqueness handled server-side, or field validation) while the transport still reports success.","triggerScenarios":"createPerson mutation returns { createPerson: null } or an object lacking id. Happens when the Person object metadata is stale, the caller lacks Person create permission, the supplied email/name fails server validation, or a create-trigger returns null.","commonSituations":"Importing a TFT contact whose email is malformed or already mapped; running the import under a role without Person create rights; workspace not re-synced after a Person field change; partial GraphQL response with errors swallowed by the SDK.","solutions":["Log the full result object to see whether createPerson is null or whether an errors array accompanied it.","Confirm the caller has Person create permission and that the Person object/fields are synced in metadata.","Validate firstName/lastName/email are non-empty and the email is well-formed before calling createPerson.","Reproduce createPerson in the GraphQL playground against the same workspace to read the server's rejection message."],"exampleFix":"// before\nconst result = await client.mutation({\n  createPerson: { __args: { data: personData }, id: true },\n});\nconst id = result.createPerson?.id;\nif (id === undefined) throw new Error('createPerson did not return an id');\n\n// after — guard input + richer error\nconst result = await client.mutation({\n  createPerson: { __args: { data: personData }, id: true },\n});\nconst id = result.createPerson?.id;\nif (id === undefined) {\n  throw new Error(\n    `createPerson did not return an id for email=${email} (result=${JSON.stringify(result)})`,\n  );\n}","handlingStrategy":"type-guard","validationCode":"import { isNonEmptyString } from 'twenty-shared';\n\nfunction assertPersonInput(input: { name?: unknown; emails?: unknown }) {\n  // name is required server-side; email strongly recommended for dedup\n  if (!input.name || typeof input.name !== 'object') {\n    throw new Error('createPerson requires a name object');\n  }\n}","typeGuard":"const hasCreatedPersonId = (r: unknown): r is { createPerson: { id: string } } =>\n  typeof r === 'object' && r !== null &&\n  typeof (r as any).createPerson?.id === 'string';\n\nif (!hasCreatedPersonId(result)) {\n  throw new Error(`createPerson returned no id: ${JSON.stringify(result)}`);\n}","tryCatchPattern":"try {\n  const id = await findOrCreatePersonId(client, input.pointOfContact, companyId);\n  // use id\n} catch (err) {\n  return { ok: false, reason: err instanceof Error ? err.message : String(err) };\n}","preventionTips":["Validate email/firstName/lastName are non-empty and email is well-formed before createPerson.","Always select and assert id on createPerson results.","Log the raw response when the id is missing to distinguish null payload from swallowed errors.","Confirm Person create permission and metadata sync."],"tags":["graphql","sdk","mutation","partners"],"backgroundTag":null,"analyzedSha":"1f5dd2bbd2a8da3419c8cfd52dd545c0024df1a6","analyzedAt":"2026-08-12T15:37:27.593Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}