{"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/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/composables/use-linked-accounts.ts#L218-L254","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the auth server config defines the provider (socialProviders entry with client id and secret)","Confirm the providerId sent matches the server-side key exactly","Verify the session is valid — if listAccounts works, the session is not the problem","Inspect the /link-social or /link/steam response body for the server's own error","For Steam, ensure the dedicated steam plugin exposing /link/steam is loaded"],"exampleFix":"// before\nstartLink('steam-oauth') // typo — no such provider on the server → 'link failed'\n\n// after\nconst configured = new Set(['google', 'github', 'discord', 'steam'])\nif (!configured.has(providerId))\n  throw new Error(`Provider ${providerId} is not configured on the auth server`)\nstartLink(providerId)","handlingStrategy":"try-catch","validationCode":"const providerIds = new Set(serverProviders.map(p => p.id))\nif (!providerIds.has(providerId))\n  return // don't call link for unknown providers","typeGuard":null,"tryCatchPattern":"try {\n  await startLink(providerId)\n}\ncatch (err) {\n  // error.value carries describeError(err) or the fallback;\n  // point the user at server-side provider config, not the browser.\n  toast('Could not start linking — check that the provider is configured on the server')\n}","preventionTips":["Configure the provider on the auth server before surfacing its button in the UI","Source the provider list from server metadata instead of hardcoding","Keep callback and errorCallbackURL origins in the server's trustedOrigins"],"tags":["better-auth","oauth","social-login","provider-config","steam"],"backgroundTag":"oauth-authorization-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}