moeru-ai/airi · error · Error

unlinkAccount failed

Error message

unlinkAccount failed

What it means

Thrown in unlink() when args.client.unlinkAccount() resolves with a non-null error. It is the server-side confirmation that the better-auth unlink endpoint failed. The generic 'unlinkAccount failed' string is used only when the backend error has no message.

Source

Thrown at packages/stage-ui/src/composables/use-linked-accounts.ts:205

  async function unlink(providerId: string, providerName: string) {
    if (inFlight.value)
      return

    if (isLastSignInMethod(providerId)) {
      error.value = args.messages.lastAccount
      message.value = null
      return
    }

    inFlight.value = providerId
    error.value = null
    message.value = null

    try {
      const { error: apiError } = await args.client.unlinkAccount({ providerId })
      if (apiError)
        throw new Error(apiError.message ?? 'unlinkAccount failed')
      message.value = args.messages.unlinked(providerName)
      args.onUnlinked?.(providerId)
      await refresh()
    }
    catch (err) {
      error.value = args.describeError(err) || args.messages.unlinkFailed
    }
    finally {
      inFlight.value = null
    }
  }

  async function link(providerId: LinkedProviderId, providerName: string) {
    if (inFlight.value)
      return

    inFlight.value = providerId
    error.value = null

View on GitHub (pinned to 27111382b4)

Solutions

  1. Read args.describeError(err) shown in error.value — it carries the backend message when present.
  2. Call refresh() to reconcile local state with the server; a stale row often disappears.
  3. Re-authenticate if the session expired, then retry unlink().
  4. If the server is consistently 500-ing, check better-auth logs for the unlink plugin error.
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side precondition already enforced by isLastSignInMethod()
if (isLastSignInMethod(providerId)) {
  // do not call unlink; server would reject with FAILED_TO_UNLINK_LAST_ACCOUNT
  return
}

Type guard

null

Try / catch

try {
  const { error: apiError } = await args.client.unlinkAccount({ providerId })
  if (apiError) throw new Error(apiError.message ?? 'unlinkAccount failed')
}
catch (err) {
  error.value = args.describeError(err) || args.messages.unlinkFailed
  // optionally call refresh() to reconcile if the row is stale
}

Prevention

When it happens

Trigger: Calling unlink(providerId, providerName) after the client-side isLastSignInMethod() guard passes, but the server rejects unlinkAccount({ providerId }). Server returns 4xx/5xx, the account row was already removed, or the providerId does not match a linked account on the server.

Common situations: Race: the account was already unlinked on another device. Auth session expired between page load and the click. Server-side better-auth plugin threw on a constraint. Network blip during the DELETE.

Related errors


AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12). Data as JSON: /api/errors/729f7036ded19644. Report an issue: GitHub.