{"record":{"id":"98828d4c6bf507ee","repo":"phacility/phabricator","slug":"authentication-provider-of-class-s-is-attempt","errorCode":null,"errorMessage":"Authentication provider (of class \"%s\") is attempting to load or create an external account, but provided a list of account identifiers which map to more than one account: %s.","messagePattern":"Authentication provider \\(of class \"(.+?)\"\\) is attempting to load or create an external account, but provided a list of account identifiers which map to more than one account: (.+?)\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"critical","filePath":"src/applications/auth/provider/PhabricatorAuthProvider.php","lineNumber":223,"sourceCode":"    }\n\n    $config = $this->getProviderConfig();\n    $viewer = PhabricatorUser::getOmnipotentUser();\n\n    $raw_identifiers = mpull($identifiers, 'getIdentifierRaw');\n\n    $accounts = id(new PhabricatorExternalAccountQuery())\n      ->setViewer($viewer)\n      ->withProviderConfigPHIDs(array($config->getPHID()))\n      ->withRawAccountIdentifiers($raw_identifiers)\n      ->needAccountIdentifiers(true)\n      ->execute();\n    if (!$accounts) {\n      $account = $this->newExternalAccount();\n    } else if (count($accounts) === 1) {\n      $account = head($accounts);\n    } else {\n      throw new Exception(\n        pht(\n          'Authentication provider (of class \"%s\") is attempting to load '.\n          'or create an external account, but provided a list of '.\n          'account identifiers which map to more than one account: %s.',\n          get_class($this),\n          implode(', ', $raw_identifiers)));\n    }\n\n    // See T13493. Add all the identifiers to the account. In the case where\n    // an account initially has a lower-quality identifier (like an email\n    // address) and later adds a higher-quality identifier (like a GUID), this\n    // allows us to automatically upgrade toward the higher-quality identifier\n    // and survive API changes which remove the lower-quality identifier more\n    // gracefully.\n\n    foreach ($identifiers as $identifier) {\n      $account->appendIdentifier($identifier);\n    }","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/auth/provider/PhabricatorAuthProvider.php#L205-L241","documentation":"Thrown from PhabricatorAuthProvider::loadOrCreateExternalAccount() when the set of raw account identifiers the provider supplies (e.g. an LDAP DN plus an email address) matches more than one PhabricatorExternalAccount row for that provider config. The code deliberately refuses to guess which account is meant and raises this Exception instead of silently merging accounts. This signals a data-integrity problem: distinct external accounts ended up sharing an identifier.","triggerScenarios":"A provider returns multiple identifiers (account GUID and email) where the GUID matches account A and the email matches account B under the same provider config; duplicate external_account rows were created by an earlier bug or interrupted login (T13493-era identifier upgrades); a user was migrated/imported twice.","commonSituations":"After an import or merge, two external accounts hold the same identifier; an LDAP server changed DN format so old and new identifiers both exist as separate accounts; a race between two concurrent logins created duplicate rows; the identifier upgrade path (adding higher-quality identifiers to an existing account) was interrupted midway.","solutions":["Find the duplicates: `SELECT accountIndex, identifier, count(*) c FROM external_account_identifier WHERE identifier IN ('<id1>','<id2>') GROUP BY identifier HAVING c > 1;` and inspect the related phabricator_externalaccount rows for the provider config.","Decide which external account is authoritative, then remove or correct the stale identifier rows (or reassign the userPHID) so each identifier maps to exactly one account — back up first.","Re-attempt the login; the single remaining match loads normally and the identifier-upgrade path reattaches cleanly.","If the duplicates belong to the same human, use the proper account-merge/admin tools rather than hand-editing, and check for orphaned sessions/tokens afterwards."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Detect ambiguity before login does: map each raw identifier to accounts\n$accounts = id(new PhabricatorExternalAccountQuery())\n  ->setViewer(PhabricatorUser::getOmnipotentUser())\n  ->withProviderConfigPHIDs(array($config->getPHID()))\n  ->withRawAccountIdentifiers($raw_identifiers)\n  ->needAccountIdentifiers(true)\n  ->execute();\nif (count($accounts) > 1) {\n  // deduplicate/repair external account identifier rows before proceeding\n}","typeGuard":null,"tryCatchPattern":"try {\n  $account = $provider->loadOrCreateExternalAccount($config, $identifiers);\n} catch (Exception $ex) {\n  // identifiers map to multiple accounts: freeze automated login retries,\n  // alert the user to contact an admin, and open a data-repair ticket\n}","preventionTips":["Monitor external_account_identifier for duplicate identifier rows after imports and merges.","Use supported merge tools rather than hand-edited SQL when consolidating accounts.","Keep provider identifier formats stable (e.g. pin LDAP DN format) to avoid forked identities."],"tags":["phabricator","auth","external-accounts","data-integrity","duplicate-identity"],"backgroundTag":"duplicate-account-identifiers","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}