Fix recovery passkey creation with locale-independent naming - #2480
Merged
Conversation
Three things kept the add-passkey step after a recovery from working,
and a fourth kept anyone from seeing it.
The name was built from `toLocaleDateString()`. Turnkey validates an
authenticator name against /^[a-zA-Z0-9 _\-:\/\.]{1,64}$/, and on an
Arabic, Persian or Bengali device that call returns non-Latin digits and
embedded RTL marks (ar-EG gives `٧/٩/٢٠٢٦`). The SDK rejected the name
before it ever showed the passkey prompt, so the step could not work at
all on those devices. Its granularity was also one day, so a second
attempt on the same date re-sent a name the account already held, which
Turnkey refuses - one failed attempt poisoned the rest of the day. An
ISO timestamp is ASCII by construction and unique per millisecond, which
is what the SDK itself does when no name is given.
The screen also offered a retry that could not succeed. The session is
minted from a single-use code and cannot be renewed here, so once it
lapses every tap fails the same way. It is now checked before the prompt
rather than after - a passkey created against a dead session is one
Turnkey never registers, and it stays on the device - and a session
failure sends the user back to the OTP step, where "Resend code" issues
a fresh one.
Finally, none of this was reportable. `addPasskey` collapses every
failure downstream of the prompt into a bare "Failed to add passkey" and
leaves the real Turnkey error on `cause`, and this flow only logged to
the console while the signup passkey flow captured to Sentry. That is
why the screenshot's error appears nowhere in Sentry. Capture it with
the Turnkey code and cause attached.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Us4dxLHKKdmSKQQ643mmaP
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the recovery passkey flow to use locale-independent naming and properly handle session expiration. The previous implementation used
toLocaleDateString()which failed on non-Latin locales (Arabic, Persian, Bengali) and had day-level granularity causing duplicate name errors on retry attempts.Key Changes
Locale-independent passkey naming: Replaced
toLocaleDateString()with ISO timestamp format (Recovery Passkey - 2026-09-07T09:51:00.000Z) that is ASCII-safe and unique per millisecondbuildRecoveryPasskeyName()utility to generate compliant namesisValidTurnkeyPasskeyName()to validate names against Turnkey's pattern before submissionSession expiration handling: Added pre-prompt session validation to prevent orphaned passpkeys
expiresAttimestamp fromverifyRecoveryOtp()hasUsableSession()check with 60-second margin before attempting passkey creationSession error detection: Added
isTurnkeySessionError()utility to distinguish session failures from retryable errorsSESSION_EXPIRED,NO_SESSION_FOUND)causechain (SDK wraps errors up to 4 levels deep)Error instrumentation: Added Sentry reporting for passkey creation failures with context (user ID, organization, session expiry, underlying cause)
Implementation Details
https://claude.ai/code/session_01Us4dxLHKKdmSKQQ643mmaP