moeru-ai/airi · error · Error
unlinkAccount failed
Error message
unlinkAccount failed
What it means
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.
Source
Thrown at packages/stage-ui/src/composables/use-linked-accounts.ts:205
async function unlink(providerId: string, providerName: string) {
if (inFlight.value)
return
if (isLastSignInMethod(providerId)) {
error.value = args.messages.lastAccount
message.value = null
return
}
inFlight.value = providerId
error.value = null
message.value = null
try {
const { error: apiError } = await args.client.unlinkAccount({ providerId })
if (apiError)
throw new Error(apiError.message ?? 'unlinkAccount failed')
message.value = args.messages.unlinked(providerName)
args.onUnlinked?.(providerId)
await refresh()
}
catch (err) {
error.value = args.describeError(err) || args.messages.unlinkFailed
}
finally {
inFlight.value = null
}
}
async function link(providerId: LinkedProviderId, providerName: string) {
if (inFlight.value)
return
inFlight.value = providerId
error.value = nullView on GitHub (pinned to 27111382b4)
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.
Defensive patterns
Strategy: try-catch
Validate before calling
// client-side precondition already enforced by isLastSignInMethod()
if (isLastSignInMethod(providerId)) {
// do not call unlink; server would reject with FAILED_TO_UNLINK_LAST_ACCOUNT
return
} Type guard
null
Try / catch
try {
const { error: apiError } = await args.client.unlinkAccount({ providerId })
if (apiError) throw new Error(apiError.message ?? 'unlinkAccount failed')
}
catch (err) {
error.value = args.describeError(err) || args.messages.unlinkFailed
// optionally call refresh() to reconcile if the row is stale
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- listAccounts 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/729f7036ded19644.
Report an issue: GitHub.