hcengineering/platform · error · PlatformError

AccountAlreadyExists

AccountAlreadyExists

Error message

An account with the provided email already exists

What it means

In signUpOtp, the email social id already resolves to a person that has an existing account, so a new signup would duplicate it. The server throws AccountAlreadyExists instead of proceeding with account creation.

Source

Thrown at server/account/src/operations.ts:374

    lastName?: string
  }
): Promise<OtpInfo> {
  const { email, firstName, lastName } = params

  if (email == null || firstName == null || email === '' || firstName === '') {
    throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
  }

  // Note: can support OTP based on any other social logins later
  const normalizedEmail = cleanEmail(email)
  let emailSocialId = await getEmailSocialId(db, normalizedEmail)
  let personUuid: PersonUuid

  if (emailSocialId !== null) {
    const existingAccount = await db.account.findOne({ uuid: emailSocialId.personUuid as AccountUuid })

    if (existingAccount !== null) {
      ctx.warn('An account with the provided email already exists', { email })
      throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountAlreadyExists, {}))
    }

    await db.person.update({ uuid: emailSocialId.personUuid }, { firstName, lastName: lastName ?? '' })

    personUuid = emailSocialId.personUuid
  } else {
    // There's no person linked to this email, so we need to create a new one
    personUuid = await db.person.insertOne({ firstName, lastName: lastName ?? '' })
    const newSocialId = { type: SocialIdType.EMAIL, value: normalizedEmail, personUuid }
    const emailSocialIdId = await db.socialId.insertOne(newSocialId)
    emailSocialId = { ...newSocialId, _id: emailSocialIdId, key: buildSocialIdString(newSocialId) }
  }

  return await sendOtp(ctx, db, branding, emailSocialId)
}

/**

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Detect the AccountAlreadyExists status and route the user to the login / password-reset flow instead of signup.
  2. Have the user sign in with the original method (social login or password) tied to that email.
  3. If the account is truly orphaned, an admin can unlink or remove the duplicate account record.
  4. Disable the signup submit button while the request is in-flight to prevent double submission.

Example fix

// before
await client.signUpOtp(email, firstName, lastName) // throws on duplicate
// after
try {
  await client.signUpOtp(email, firstName, lastName)
} catch (e) {
  if (isStatusError(e, platform.status.AccountAlreadyExists)) {
    redirectToLogin(email)
    return
  }
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await lookupAccountByEmail(email)
if (existing != null) redirectToLogin(email)

Type guard

function isAccountAlreadyExists(e: unknown): boolean {
  return e instanceof PlatformError && e.status.code === platform.status.AccountAlreadyExists
}

Try / catch

try {
  await client.signUpOtp(email, firstName, lastName)
} catch (e) {
  if (isAccountAlreadyExists(e)) {
    redirectToLogin(email)
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Calling signUpOtp with an email whose social id (emailSocialId) is already linked to an account — i.e. the person already completed signup, or a partial signup previously created the account.

Common situations: Double-submitting the signup form; retrying signup after a timeout when the first request actually succeeded; users signing up again after switching login methods; migration leaving a verified social id attached to an existing account.

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/be80f80b61b3b95a. Report an issue: GitHub.