{"record":{"id":"729f7036ded19644","repo":"moeru-ai/airi","slug":"unlinkaccount-failed","errorCode":null,"errorMessage":"unlinkAccount failed","messagePattern":"unlinkAccount failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/composables/use-linked-accounts.ts","lineNumber":205,"sourceCode":"\n  async function unlink(providerId: string, providerName: string) {\n    if (inFlight.value)\n      return\n\n    if (isLastSignInMethod(providerId)) {\n      error.value = args.messages.lastAccount\n      message.value = null\n      return\n    }\n\n    inFlight.value = providerId\n    error.value = null\n    message.value = null\n\n    try {\n      const { error: apiError } = await args.client.unlinkAccount({ providerId })\n      if (apiError)\n        throw new Error(apiError.message ?? 'unlinkAccount failed')\n      message.value = args.messages.unlinked(providerName)\n      args.onUnlinked?.(providerId)\n      await refresh()\n    }\n    catch (err) {\n      error.value = args.describeError(err) || args.messages.unlinkFailed\n    }\n    finally {\n      inFlight.value = null\n    }\n  }\n\n  async function link(providerId: LinkedProviderId, providerName: string) {\n    if (inFlight.value)\n      return\n\n    inFlight.value = providerId\n    error.value = null","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/composables/use-linked-accounts.ts#L187-L223","documentation":"Fallback message when client.unlinkAccount({ providerId }) returns an error result with no message of its own. The composable's guard already blocks unlinking the last sign-in method, so reaching this throw means the server rejected the call itself — most often because the account row no longer exists server-side (stale UI list) or the session expired. The catch sets error.value for display.","triggerScenarios":"Unlinking a provider that was already removed (list went stale after unlinking in another tab or after re-login elsewhere); expired session cookie on the unlink mutation; a better-auth server error while deleting the provider record.","commonSituations":"Two settings tabs open and one already unlinked; user re-linked the provider from another device; auth service restarted mid-session.","solutions":["Call refresh() and retry the unlink once — a stale list is the most common cause","Re-authenticate if the session expired, then unlink again","Verify the providerId being sent matches a row currently present in linkedAccounts","Check auth server logs for the unlink route if the failure persists"],"exampleFix":"// before\nawait unlink(providerId) // 'unlinkAccount failed' because the list went stale\n\n// after\nif (!linkedAccounts.value.some(a => a.providerId === providerId)) {\n  await refresh()\n  return\n}\nawait unlink(providerId)","handlingStrategy":"try-catch","validationCode":"const stillLinked = linkedAccounts.value.some(a => a.providerId === providerId)\nif (!stillLinked) {\n  await refresh() // resync before hitting the server\n}","typeGuard":null,"tryCatchPattern":"try {\n  await unlink(providerId)\n}\ncatch {\n  // error.value is already set by the composable; offer a retry that\n  // refreshes first, since stale lists are the dominant cause.\n  showRetryDialog(() => { refresh(); unlink(providerId) })\n}","preventionTips":["Refresh the account list right before unlink actions","Disable the unlink button for rows absent from the current list","Handle session expiry globally for all auth mutations"],"tags":["better-auth","unlink","linked-accounts","stale-state"],"backgroundTag":"auth-request-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}