moeru-ai/airi · error · Error
listAccounts failed
Error message
listAccounts failed
What it means
Thrown inside refresh() when the better-auth typed client's listAccounts() resolves with a non-null `error` object. The composable only falls back to the generic 'listAccounts failed' string when the server error carried no message. It surfaces backend auth-service failures to the UI error ref rather than silently showing an empty list.
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 27111382b4)
Solutions
- Check the auth service health and the network tab: the real status/message is in apiError.message — inspect `error.value` in the composable.
- If the session expired, re-authenticate so the credentialed/Bearer client regains a valid session, then call refresh() again.
- Verify better-auth base URL and CORS configuration allow the requesting origin.
- Treat transient 5xx as retryable: the composable intentionally keeps prior linkedAccounts on error, so a manual refresh() after the outage recovers.
Defensive patterns
Strategy: try-catch
Validate before calling
// refresh() already clears error and sets loading; pre-check auth before mount
if (!args.isAuthenticated.value) {
// skip refresh; listAccounts requires an active session
} Type guard
null
Try / catch
// already implemented in refresh(): error surfaced via error.value, prior list kept
try {
const { data, error: apiError } = await args.client.listAccounts()
if (apiError) throw new Error(apiError.message ?? 'listAccounts failed')
// ...
}
catch (err) {
error.value = args.describeError(err) || args.messages.listFailed
} Prevention
- Only call refresh() when isAuthenticated is true.
- Keep prior linkedAccounts on error so the UI does not blank out on a transient 5xx.
- Treat 401 as a re-auth trigger rather than a permanent failure.
When it happens
Trigger: args.client.listAccounts() returns { data: null, error: { ... } }. This happens when the auth backend returns non-2xx (5xx outage, 401 expired session cookie, 403 forbidden), the network request fails and better-auth wraps it, or the auth service URL is misconfigured/unreachable.
Common situations: Auth service is down or restarting. The user's session cookie expired and silent refresh also failed (stage-web Bearer token revoked). The better-auth base URL env var points at the wrong host. CORS blocks the request in stage-web.
Related errors
- unlinkAccount failed
- link failed
- Auth request failed (${response.status})
- Token exchange failed (${response.status}): ${text}
- Auth request failed (${error.status ?? 'unknown'})
AI-assisted analysis of moeru-ai/airi@27111382b4 (2026-08-12).
Data as JSON: /api/errors/9640cb85aaf259eb.
Report an issue: GitHub.