{"record":{"id":"9640cb85aaf259eb","repo":"moeru-ai/airi","slug":"listaccounts-failed","errorCode":null,"errorMessage":"listAccounts failed","messagePattern":"listAccounts failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/composables/use-linked-accounts.ts","lineNumber":162,"sourceCode":"  )\n\n  /**\n   * Client-side mirror of better-auth's `FAILED_TO_UNLINK_LAST_ACCOUNT`\n   * guard so we can surface a user-friendly message before round-tripping.\n   */\n  function isLastSignInMethod(providerId: string): boolean {\n    if (providerId === 'credential')\n      return socialLinkedCount.value === 0\n    return !hasCredentialAccount.value && socialLinkedCount.value <= 1\n  }\n\n  async function refresh() {\n    loading.value = true\n    error.value = null\n    try {\n      const { data, error: apiError } = await args.client.listAccounts()\n      if (apiError)\n        throw new Error(apiError.message ?? 'listAccounts failed')\n      // better-auth 1.6.6 widens listAccounts elements to `any`; consume\n      // the row directly rather than dressing `any` up with a fake shape.\n      // Field layout: node_modules/better-auth/dist/api/routes/account.mjs L20-50.\n      linkedAccounts.value = (data ?? []).map(account => ({\n        id: account.id,\n        accountId: account.accountId,\n        providerId: account.providerId,\n        createdAt: account.createdAt instanceof Date\n          ? account.createdAt.toISOString()\n          : account.createdAt,\n        scopes: account.scopes ?? [],\n      }))\n      loaded.value = true\n    }\n    catch (err) {\n      // Keep prior `linkedAccounts` on error so a transient 5xx doesn't\n      // flip `hasCredentialAccount` and mis-route the password UI.\n      // Source: PR #1753 review (chatgpt-codex-connector P2).","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/composables/use-linked-accounts.ts#L144-L180","documentation":"Fallback message used in refresh() when the better-auth client's listAccounts() call resolves with an error object whose own message is empty. The throw is caught by the composable and written to its error ref, which the linked-accounts settings UI renders. Root causes are transport- or session-level: expired auth cookie, auth server unreachable, or a proxy interfering with the account list endpoint.","triggerScenarios":"Calling client.listAccounts() with an expired or revoked session cookie; the standalone auth service being down so the client surfaces a network error result; a reverse proxy or CORS layer returning an error status for the list-accounts route; a better-auth server version that renamed or removed the route.","commonSituations":"Dev server restart invalidated the session; auth service stopped while the settings page loads; cookies blocked in an embedded webview or iframe (SameSite); user signed out in another tab.","solutions":["Sign out and back in to mint a fresh session, then reopen the linked-accounts page","Verify the auth server the client points at is running and its base URL is correct","Inspect the network tab for the list-accounts request: 401 means session, 5xx means server","If behind a proxy, confirm cookies and CORS headers survive for the auth origin"],"exampleFix":"// before\nconst { data, error } = await args.client.listAccounts() // opaque 'listAccounts failed'\n\n// after\nconst session = await args.client.getSession()\nif (!session) {\n  error.value = 'Session expired — sign in again'\n  return\n}\nconst { data, error } = await args.client.listAccounts()","handlingStrategy":"try-catch","validationCode":"const session = await args.client.getSession()\nif (!session)\n  return // skip listAccounts until re-authenticated","typeGuard":null,"tryCatchPattern":"try {\n  await refresh()\n}\ncatch (err) {\n  // error.value already carries describeError(err) or the fallback;\n  // treat session expiry as a redirect, everything else as a toast.\n  if (isAuthExpired(err))\n    router.push('/sign-in')\n}","preventionTips":["Check getSession() before rendering the linked-accounts panel","Keep the auth service and app on matching better-auth versions","Configure cookie SameSite and CORS for the auth origin when proxying","Refresh account data on window focus to recover from stale sessions"],"tags":["better-auth","linked-accounts","session","api"],"backgroundTag":"auth-session-expired","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"}