moeru-ai/airi · error · Error

link failed

Error message

link failed

What it means

Fallback message when better-auth's link initiation call (linkSocial for OAuth2 providers, or linkSteam for the OpenID 2.0 Steam flow) returns an error with no message. The happy path returns data.url and the code navigates the browser there, so this throw means the server refused to start the link at all. Typical causes are server-side provider misconfiguration or an unknown providerId.

Solutions

  1. Check the auth server config defines the provider (socialProviders entry with client id and secret)
  2. Confirm the providerId sent matches the server-side key exactly
  3. Verify the session is valid — if listAccounts works, the session is not the problem
  4. Inspect the /link-social or /link/steam response body for the server's own error
  5. For Steam, ensure the dedicated steam plugin exposing /link/steam is loaded

Example fix

// before
startLink('steam-oauth') // typo — no such provider on the server → 'link failed'

// after
const configured = new Set(['google', 'github', 'discord', 'steam'])
if (!configured.has(providerId))
  throw new Error(`Provider ${providerId} is not configured on the auth server`)
startLink(providerId)
Defensive patterns

Strategy: try-catch

Validate before calling

const providerIds = new Set(serverProviders.map(p => p.id))
if (!providerIds.has(providerId))
  return // don't call link for unknown providers

Try / catch

try {
  await startLink(providerId)
}
catch (err) {
  // error.value carries describeError(err) or the fallback;
  // point the user at server-side provider config, not the browser.
  toast('Could not start linking — check that the provider is configured on the server')
}

Prevention

When it happens

Trigger: Provider not configured in better-auth server options (missing client id/secret env vars); providerId string not matching any server-side socialProvider key; session expired so the link route returns 401; the steam plugin absent so /link/steam 404s; callbackURL rejected by the server's trustedOrigins.

Common situations: Adding a new social login without setting its env vars on the auth service; frontend and backend disagreeing on provider naming; deploying UI before the auth server update that adds the steam plugin.

Related errors


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

Appendix: source

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

  async function link(providerId: LinkedProviderId, providerName: string) {
    if (inFlight.value)
      return

    inFlight.value = providerId
    error.value = null
    message.value = args.messages.linkStarted(providerName)

    try {
      const callbackURL = args.buildCallbackURL ? args.buildCallbackURL() : window.location.href
      // Steam is OpenID 2.0, not OAuth2 — the server steam plugin exposes a
      // dedicated `/link/steam` endpoint, surfaced as `linkSteam` by the
      // client plugin. Every other provider uses `/link-social`.
      const result = providerId === 'steam'
        ? await args.client.linkSteam({ callbackURL, errorCallbackURL: callbackURL })
        : await args.client.linkSocial({ provider: providerId, callbackURL, errorCallbackURL: callbackURL })
      const { data, error: apiError } = result
      if (apiError)
        throw new Error(apiError.message ?? 'link failed')
      if (data?.url) {
        args.onLinkStarted?.(providerId)
        window.location.assign(data.url)
        return
      }
      // No URL came back (e.g. provider returned success synchronously) —
      // refresh so the new row shows up without a navigation.
      args.onLinkStarted?.(providerId)
      await refresh()
    }
    catch (err) {
      error.value = args.describeError(err) || args.messages.linkFailed
      message.value = null
      inFlight.value = null
    }
  }

  // Auto-refresh: load on mount when already authed; react to sign-in /

View on GitHub (pinned to 677329427f)