{"record":{"id":"8fc1b0399e77551d","repo":"moeru-ai/airi","slug":"link-failed","errorCode":null,"errorMessage":"link failed","messagePattern":"link failed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/composables/use-linked-accounts.ts","lineNumber":236,"sourceCode":"  async function link(providerId: LinkedProviderId, providerName: string) {\n    if (inFlight.value)\n      return\n\n    inFlight.value = providerId\n    error.value = null\n    message.value = args.messages.linkStarted(providerName)\n\n    try {\n      const callbackURL = args.buildCallbackURL ? args.buildCallbackURL() : window.location.href\n      // Steam is OpenID 2.0, not OAuth2 — the server steam plugin exposes a\n      // dedicated `/link/steam` endpoint, surfaced as `linkSteam` by the\n      // client plugin. Every other provider uses `/link-social`.\n      const result = providerId === 'steam'\n        ? await args.client.linkSteam({ callbackURL, errorCallbackURL: callbackURL })\n        : await args.client.linkSocial({ provider: providerId, callbackURL, errorCallbackURL: callbackURL })\n      const { data, error: apiError } = result\n      if (apiError)\n        throw new Error(apiError.message ?? 'link failed')\n      if (data?.url) {\n        args.onLinkStarted?.(providerId)\n        window.location.assign(data.url)\n        return\n      }\n      // No URL came back (e.g. provider returned success synchronously) —\n      // refresh so the new row shows up without a navigation.\n      args.onLinkStarted?.(providerId)\n      await refresh()\n    }\n    catch (err) {\n      error.value = args.describeError(err) || args.messages.linkFailed\n      message.value = null\n      inFlight.value = null\n    }\n  }\n\n  // Auto-refresh: load on mount when already authed; react to sign-in /","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/moeru-ai/airi/blob/27111382b4a79a7e983289d6e983a06af185ed0f/packages/stage-ui/src/composables/use-linked-accounts.ts#L218-L254","documentation":"Thrown in link() when either linkSocial() (OAuth2 providers) or linkSteam() (OpenID 2.0) resolves with a non-null error. It indicates the better-auth/social or steam server plugin rejected the link-start request before issuing a redirect URL. 'link failed' is the fallback string when the backend error has no message.","triggerScenarios":"Calling link(providerId, providerName) where linkSocial({ provider, callbackURL, errorCallbackURL }) or linkSteam({ callbackURL, errorCallbackURL }) returns an error object. Server-side provider not configured in socialProviders, callbackURL not in the allow-list, the steam plugin not registered, or the user is not authenticated.","commonSituations":"Provider disabled/missing in better-auth config (e.g. Google client ID/secret unset). Redirect/callback URL not allowlisted on the server. Steam plugin not loaded. Auth session expired before the click.","solutions":["Inspect error.value (args.describeError) for the backend message — it usually names the missing config.","Ensure the provider is configured in better-auth socialProviders and the Steam plugin is registered for providerId === 'steam'.","Confirm callbackURL is allowlisted; buildCallbackURL must return a URL the server accepts.","Re-authenticate if the session lapsed, then retry link()."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// confirm the provider is configured server-side before offering it\nif (!knownSocialProviders.includes(providerId) && providerId !== 'steam')\n  // do not attempt link; provider not registered\n","typeGuard":"null","tryCatchPattern":"try {\n  const result = providerId === 'steam'\n    ? await args.client.linkSteam({ callbackURL, errorCallbackURL: callbackURL })\n    : await args.client.linkSocial({ provider: providerId, callbackURL, errorCallbackURL: callbackURL })\n  const { data, error: apiError } = result\n  if (apiError) throw new Error(apiError.message ?? 'link failed')\n}\ncatch (err) {\n  error.value = args.describeError(err) || args.messages.linkFailed\n}","preventionTips":["Ensure each provider is configured in better-auth socialProviders and the Steam plugin is registered.","Allowlist the callbackURL/buildCallbackURL output server-side.","Serialize link attempts via the inFlight guard to avoid overlapping OAuth redirects."],"tags":["network","better-auth","oauth","openid","auth","linked-accounts"],"backgroundTag":null,"analyzedSha":"27111382b4a79a7e983289d6e983a06af185ed0f","analyzedAt":"2026-08-12T18:33:34.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}