phacility/phabricator · error · PhabricatorApplicationTransactionValidationException
The email address "%s" is already attached to this account.
Error message
The email address "%s" is already attached to this account.
What it means
PhortuneAccountEmailEditor catches the duplicate-key failure from the storage layer when saving a PhortuneAccountEmailAddress whose address already exists on the account, and rethrows it as a PhabricatorApplicationTransactionValidationException with a 'Duplicate' field error naming the offending address. This is a designed validation path: the unique constraint on (account, address) is enforced at the database level and translated into a user-facing transaction error rather than a raw crash.
Source
Thrown at src/applications/phortune/editor/PhortuneAccountEmailEditor.php:33
return pht('%s created this account email.', $author);
}
protected function didCatchDuplicateKeyException(
PhabricatorLiskDAO $object,
array $xactions,
Exception $ex) {
$errors = array();
$errors[] = new PhabricatorApplicationTransactionValidationError(
PhortuneAccountEmailAddressTransaction::TRANSACTIONTYPE,
pht('Duplicate'),
pht(
'The email address "%s" is already attached to this account.',
$object->getAddress()),
null);
throw new PhabricatorApplicationTransactionValidationException($errors);
}
}
View on GitHub (pinned to 5720a38cfe)
Solutions
- Before applying, load the account's existing email addresses and skip/flag any address already attached (dedupe on the client of the editor).
- Catch PhabricatorApplicationTransactionValidationException in the controller and render getErrors() as form field errors — that is exactly what the exception carries.
- Guard the form against double submission (disable the button on submit, or accept a one-time token) so the second insert never happens.
- If the address exists but is unverified, resend verification to the existing row instead of creating a second one.
Example fix
// before
id(new PhortuneAccountEmailEditor())
->setActor($viewer)
->setContentSource($content_source)
->applyTransactions($email, $xactions);
// after
$existing = id(new PhortuneAccountEmailQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withAddresses(array($raw_address))
->execute();
if ($existing) {
$e_address = pht('Duplicate');
$errors[] = pht(
'The email address "%s" is already attached to this account.',
$raw_address);
return $this->buildEditorResponse($errors);
}
id(new PhortuneAccountEmailEditor())
->setActor($viewer)
->setContentSource($content_source)
->applyTransactions($email, $xactions); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight: does this address already exist on the account?
$attached = id(new PhortuneAccountEmailQuery())
->setViewer($viewer)
->withAccountPHIDs(array($account->getPHID()))
->withAddresses(array($address))
->execute();
if ($attached) {
// Show the duplicate error in the form; do not apply transactions.
$e_address = pht('Duplicate');
$errors[] = pht(
'The email address "%s" is already attached to this account.',
$address);
} Try / catch
try {
id(new PhortuneAccountEmailEditor())
->setActor($viewer)
->setContentSource($content_source)
->applyTransactions($object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
// The editor already packaged the duplicate as a field error;
// re-render the form with $ex->getErrors() instead of crashing.
return $this->buildFormResponse($object, $ex->getErrors());
} Prevention
- Disable the submit button on first click so double submissions never reach the editor.
- Normalize addresses (trim; lowercase domain) before comparing or inserting.
- For re-adds of unverified addresses, resend verification on the existing row rather than creating a new one.
- Treat a unique-index violation in editors as a validation result, never as an unexpected 500.
When it happens
Trigger: Applying an edit transaction (TRANSACTIONTYPE on PhortuneAccountEmailAddressTransaction) that adds an address equal to one already attached to the same Phortune account — e.g. re-adding 'billing@example.com' after it was already verified. The catch block fires when insert/update hits the unique index and Lisk raises a duplicate exception.
Common situations: Double form submission or impatient re-click on 'Add Email'; re-inviting an address that is attached but not yet verified; copy-paste retries after a session-expired error page; scripts replaying account-email edits idempotently without deduping.
Related errors
- Service "%s" is unrecognized, restricted, or you do not have
- When creating a new Almanac interface via the Conduit API, y
- Device "%s" is unrecognized, restricted, or you do not have
- Interfaces must have a unique combination of network, device
- Another namespace with this name already exists. Each namesp
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/30ee440a9d2e6eae.
Report an issue: GitHub.