{"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/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/composables/use-linked-accounts.ts#L187-L223","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read args.describeError(err) shown in error.value — it carries the backend message when present.","Call refresh() to reconcile local state with the server; a stale row often disappears.","Re-authenticate if the session expired, then retry unlink().","If the server is consistently 500-ing, check better-auth logs for the unlink plugin error."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// client-side precondition already enforced by isLastSignInMethod()\nif (isLastSignInMethod(providerId)) {\n  // do not call unlink; server would reject with FAILED_TO_UNLINK_LAST_ACCOUNT\n  return\n}","typeGuard":"null","tryCatchPattern":"try {\n  const { error: apiError } = await args.client.unlinkAccount({ providerId })\n  if (apiError) throw new Error(apiError.message ?? 'unlinkAccount failed')\n}\ncatch (err) {\n  error.value = args.describeError(err) || args.messages.unlinkFailed\n  // optionally call refresh() to reconcile if the row is stale\n}","preventionTips":["Guard unlink with isLastSignInMethod() to avoid the obvious server rejection.","Refresh the list after errors to reconcile stale rows.","Disable the unlink button while inFlight is set to prevent duplicate concurrent calls."],"tags":["network","better-auth","auth","linked-accounts","api-error"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}