moeru-ai/airi · error · Error

listAccounts failed

Error message

listAccounts failed

What it means

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.

Solutions

  1. Sign out and back in to mint a fresh session, then reopen the linked-accounts page
  2. Verify the auth server the client points at is running and its base URL is correct
  3. Inspect the network tab for the list-accounts request: 401 means session, 5xx means server
  4. If behind a proxy, confirm cookies and CORS headers survive for the auth origin

Example fix

// before
const { data, error } = await args.client.listAccounts() // opaque 'listAccounts failed'

// after
const session = await args.client.getSession()
if (!session) {
  error.value = 'Session expired — sign in again'
  return
}
const { data, error } = await args.client.listAccounts()
Defensive patterns

Strategy: try-catch

Validate before calling

const session = await args.client.getSession()
if (!session)
  return // skip listAccounts until re-authenticated

Try / catch

try {
  await refresh()
}
catch (err) {
  // error.value already carries describeError(err) or the fallback;
  // treat session expiry as a redirect, everything else as a toast.
  if (isAuthExpired(err))
    router.push('/sign-in')
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/9640cb85aaf259eb. Report an issue: GitHub.

Appendix: source

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

  )

  /**
   * Client-side mirror of better-auth's `FAILED_TO_UNLINK_LAST_ACCOUNT`
   * guard so we can surface a user-friendly message before round-tripping.
   */
  function isLastSignInMethod(providerId: string): boolean {
    if (providerId === 'credential')
      return socialLinkedCount.value === 0
    return !hasCredentialAccount.value && socialLinkedCount.value <= 1
  }

  async function refresh() {
    loading.value = true
    error.value = null
    try {
      const { data, error: apiError } = await args.client.listAccounts()
      if (apiError)
        throw new Error(apiError.message ?? 'listAccounts failed')
      // better-auth 1.6.6 widens listAccounts elements to `any`; consume
      // the row directly rather than dressing `any` up with a fake shape.
      // Field layout: node_modules/better-auth/dist/api/routes/account.mjs L20-50.
      linkedAccounts.value = (data ?? []).map(account => ({
        id: account.id,
        accountId: account.accountId,
        providerId: account.providerId,
        createdAt: account.createdAt instanceof Date
          ? account.createdAt.toISOString()
          : account.createdAt,
        scopes: account.scopes ?? [],
      }))
      loaded.value = true
    }
    catch (err) {
      // Keep prior `linkedAccounts` on error so a transient 5xx doesn't
      // flip `hasCredentialAccount` and mis-route the password UI.
      // Source: PR #1753 review (chatgpt-codex-connector P2).

View on GitHub (pinned to 677329427f)