{"record":{"id":"cbf4749cce8b3d85","repo":"antiwork/gumroad","slug":"we-couldn-t-sign-you-in-with-that-passkey-please","errorCode":null,"errorMessage":"We couldn't sign you in with that passkey. Please try again or use your password.","messagePattern":"We couldn't sign you in with that passkey\\. Please try again or use your password\\.","errorType":"exception","errorClass":"ResponseError","httpStatus":null,"severity":"error","filePath":"app/javascript/pages/Logins/New.tsx","lineNumber":106,"sourceCode":"      signal?: AbortSignal;\n      surfaceErrors: boolean;\n    }) => {\n      try {\n        let options = embeddedOptions;\n        if (!options) {\n          const optionsResponse = await request({\n            url: Routes.login_passkey_options_path(),\n            method: \"POST\",\n            accept: \"json\",\n            abortSignal: signal,\n          });\n          const optionsResult = typia.assert<{\n            success: boolean;\n            options?: PasskeyAuthenticationOptions;\n            error_message?: string;\n          }>(await optionsResponse.json());\n          if (!optionsResponse.ok || !optionsResult.success || !optionsResult.options) {\n            throw new ResponseError(optionsResult.error_message ?? PASSKEY_ERROR);\n          }\n          options = optionsResult.options;\n        }\n\n        const credential = await getPasskey(options, { mediation, signal });\n\n        setPasskeyLoading(true);\n\n        const loginResponse = await request({\n          url: Routes.login_passkey_path(),\n          method: \"POST\",\n          accept: \"json\",\n          data: { credential, next },\n          abortSignal: signal,\n        });\n        const loginResult = typia.assert<{ success: boolean; redirect_location?: string; error_message?: string }>(\n          await loginResponse.json(),\n        );","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/pages/Logins/New.tsx#L88-L124","documentation":"Fallback shown when the first step of passkey sign-in on /login fails: POSTing to Routes.login_passkey_options_path to fetch WebAuthn authentication options. It throws ResponseError with the server's error_message when !optionsResponse.ok, success is false, or options is missing — or when typia.assert rejects a malformed body. The catch deliberately swallows AbortError and browser DOMException NotAllowedError/AbortError, so user cancellations of the native passkey sheet never surface this message; only genuine fetch/option failures set the visible passkey error.","triggerScenarios":"POST /login/passkey_options returns 4xx/5xx (rate limit, CSRF/session problem, server error), success:false (no credentials match), options omitted, or the response body drifts from the asserted { success, options, error_message } shape.","commonSituations":"Conditional-UI/usernameless autofall kicking off before cookies are ready, Rails route or serialization changes breaking the options contract, aggressive rate limiting on repeated attempts, or users with no registered passkeys pressing the passkey button.","solutions":["Retry the passkey button — transient option-fetch failures are common.","If it persists, use password sign-in (the message's own fallback).","Devtools: check the passkey_options POST status and body — a server error_message would have been shown verbatim, so this fallback means the body had none.","Maintainers: keep the { success, options, error_message } shape stable; typia violations surface as this same generic fallback."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const isOptionsFetchFailure = (\n  response: Response,\n  result: { success?: boolean; options?: PasskeyAuthenticationOptions },\n): boolean => !response.ok || result.success !== true || result.options == null;","tryCatchPattern":"try {\n  const optionsResponse = await request({ url: Routes.login_passkey_options_path(), method: \"POST\", accept: \"json\", abortSignal: signal });\n  const optionsResult = typia.assert<{ success: boolean; options?: PasskeyAuthenticationOptions; error_message?: string }>(await optionsResponse.json());\n  if (!optionsResponse.ok || !optionsResult.success || !optionsResult.options) {\n    throw new ResponseError(optionsResult.error_message ?? PASSKEY_ERROR);\n  }\n} catch (e) {\n  if (!signal?.aborted) setLoading(false);\n  if (e instanceof AbortError) return;\n  if (e instanceof DOMException && (e.name === \"NotAllowedError\" || e.name === \"AbortError\")) return; // user dismissed the native sheet\n  if (surfaceErrors) setPasskeyError(e instanceof ResponseError ? e.message : PASSKEY_ERROR);\n}","preventionTips":["Always pass an AbortSignal so cancellations don't surface as user-visible errors.","Swallow NotAllowedError — the user closing the native prompt is not a failure.","Keep the options endpoint cheap and unthrottled so first-hit reliability is high."],"tags":["passkeys","webauthn","authentication","login","http","typia"],"backgroundTag":"webauthn-authentication-failed","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}