') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); fix(ui): avoid re-preparing pending code verifications by jacekradko · Pull Request #8548 · clerk/javascript · GitHub
Skip to content

fix(ui): avoid re-preparing pending code verifications - #8548

Merged
jacekradko merged 10 commits into
mainfrom
jacek/fix-pending-otp-reprepare
May 29, 2026
Merged

fix(ui): avoid re-preparing pending code verifications#8548
jacekradko merged 10 commits into
mainfrom
jacek/fix-pending-otp-reprepare

Conversation

@jacekradko

@jacekradkojacekradko commented May 14, 2026

Copy link
Copy Markdown
Contributor

When the sign-in or sign-up flow rehydrates a session that already has a pending email/phone code verification, the code-entry components were calling prepare* on mount and sending the user a second OTP. This skips the initial prepare in that case while leaving the explicit "Resend code" path untouched, so users only get a new code when they ask for one.

Closes: #8463

@changeset-bot

changeset-botBot commented May 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3edd0e4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
NameType
@clerk/uiPatch
@clerk/chrome-extensionPatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentMay 28, 2026 5:25pm

Request Review

@coderabbitai

coderabbitaiBot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds deduplication logic across SignIn and SignUp verification flows to prevent re-preparing verification codes when a matching pending (unverified) verification already exists. SignInFactorOneCodeForm now detects when a pending first-factor phone code verification is present and skips automatic preparation. SignUpEmailCodeCard and SignUpPhoneCodeCard similarly detect pending email or phone code verifications and skip re-preparation in those cases. Tests validate the new behavior for both pending and expired verification states.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description check✅ PassedThe description clearly explains the problem (duplicate OTPs on mount/refresh) and solution (skip initial prepare for pending verifications while preserving explicit Resend behavior).
Linked Issues check✅ PassedThe PR comprehensively addresses issue #8463 by implementing the suggested fix: detecting pending verifications on mount and skipping prepare calls for SignIn/SignUp code-entry components.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing the pending OTP reprepare regression: component state logic, tests, and integration tests validating the fix without unrelated modifications.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check✅ PassedThe PR title accurately summarizes the main objective: preventing duplicate OTP preparation when code verifications are already pending, which is the core fix across all modified components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-newBot commented May 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8548

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8548

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8548

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8548

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8548

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8548

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8548

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8548

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8548

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8548

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8548

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8548

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8548

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8548

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8548

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8548

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8548

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8548

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8548

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8548

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8548

commit: 3edd0e4

@github-actions

Copy link
Copy Markdown
Contributor

Snapi: no API changes detected in @clerk/backend, @clerk/clerk-js, @clerk/nextjs, @clerk/react, @clerk/shared, @clerk/ui.

@jacekradkojacekradko changed the title fix(ui): avoid repreparing pending code verificationsfix(ui): avoid re-preparing pending code verificationsMay 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

<SignIn> sends a new email OTP on every mount/refresh while a pending verification already exists

2 participants

@jacekradko@Ephem