phacility/phabricator · error · Exception

Password providers can't be linked.

Error message

Password providers can't be linked.

What it means

PhabricatorPasswordAuthProvider::buildLinkForm() throws unconditionally. The built-in password provider is a primary credential store, not an external identity source, so 'linking' it to an already logged-in account is a meaningless operation that Phabricator refuses rather than rendering a form for.

Source

Thrown at src/applications/auth/provider/PhabricatorPasswordAuthProvider.php:163

      ->setUser($viewer)
      ->addHiddenInput('invite', true)
      ->appendChild(
        id(new AphrontFormTextControl())
          ->setLabel(pht('Username'))
          ->setName('username'));

    $dialog = id(new AphrontDialogView())
      ->setUser($viewer)
      ->setTitle(pht('Register an Account'))
      ->appendForm($form)
      ->setSubmitURI('/auth/register/')
      ->addSubmitButton(pht('Continue'));

    return $dialog;
  }

  public function buildLinkForm($controller) {
    throw new Exception(pht("Password providers can't be linked."));
  }

  private function renderPasswordLoginForm(
    AphrontRequest $request,
    $require_captcha = false,
    $captcha_valid = false) {

    $viewer = $request->getUser();

    $dialog = id(new AphrontDialogView())
      ->setSubmitURI($this->getLoginURI())
      ->setUser($viewer)
      ->setTitle(pht('Log In'))
      ->addSubmitButton(pht('Log In'));

    if ($this->shouldAllowRegistration()) {
      $dialog->addCancelButton(
        '/auth/register/',

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Skip password providers before building link forms (instanceof PhabricatorPasswordAuthProvider check or provider key comparison)
  2. In custom UIs, only offer 'link account' for providers that actually implement an external handshake (OAuth/LDAP providers)
  3. If you reached this via a URL, stop hand-editing /auth/link/ URIs and use the account settings UI

Example fix

// before
foreach ($providers as $provider) {
  $forms[] = $provider->buildLinkForm($controller); // fatal for password provider
}

// after
foreach ($providers as $provider) {
  if ($provider instanceof PhabricatorPasswordAuthProvider) {
    continue;
  }
  $forms[] = $provider->buildLinkForm($controller);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Before rendering link forms across all enabled providers:
foreach ($providers as $provider) {
  if ($provider instanceof PhabricatorPasswordAuthProvider) {
    continue; // primary credential provider: linking not applicable
  }
  $forms[] = $provider->buildLinkForm($controller);
}

Type guard

function providerSupportsLinking(PhabricatorAuthProvider $provider) {
  return !($provider instanceof PhabricatorPasswordAuthProvider);
}

Try / catch

try {
  $form = $provider->buildLinkForm($controller);
} catch (Exception $ex) {
  // Password provider hit by mistake: skip instead of fatal.
  return null;
}

Prevention

When it happens

Trigger: Calling buildLinkForm() on a PhabricatorPasswordAuthProvider instance: custom code that iterates all enabled providers and renders link forms for each, or a user manually navigating to the account-linking URI (/auth/link/...) for the password provider.

Common situations: Extensions or custom controllers that assume every auth provider supports account linking; users hand-editing auth URLs; tooling that enumerates providers and calls every build*Form method.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/0592a9d762d942fd. Report an issue: GitHub.