Skip to content

fix(clerk-js): Add retry logic and race condition fixes for Cloudflar…#7766

Open
braden-clerk wants to merge 1 commit intomainfrom
fix-captcha-200100-race-condition
Open

fix(clerk-js): Add retry logic and race condition fixes for Cloudflar…#7766
braden-clerk wants to merge 1 commit intomainfrom
fix-captcha-200100-race-condition

Conversation

@braden-clerk
Copy link

@braden-clerk braden-clerk commented Feb 4, 2026

…e captcha error 200100

This fixes Cloudflare Turnstile error 200100 ("Widget not found") which occurs when the captcha container element isn't available during rendering.

Changes:

  • Add '200' to shouldRetryTurnstileErrorCode to retry all 200xxx errors (including 200100)
  • Add waitForElement() call before captcha.render() to verify container exists
  • Update smart widget initialization to use waitForElement() instead of getElementById()
  • Add test coverage for error codes 200, 200100, and 200xxx

These changes prevent race conditions where DOM elements are removed or not ready between checks and render calls, particularly during React/framework re-renders.

Description

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • [ X ] 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced CAPTCHA error retry logic to handle additional error codes, improving reliability during network issues.
    • Improved CAPTCHA widget loading by implementing better element existence verification before initialization, preventing rendering errors when elements are not immediately available.

…e captcha error 200100

This fixes Cloudflare Turnstile error 200100 ("Widget not found") which occurs when the captcha container element isn't available during rendering.

Changes:
- Add '200' to shouldRetryTurnstileErrorCode to retry all 200xxx errors (including 200100)
- Add waitForElement() call before captcha.render() to verify container exists
- Update smart widget initialization to use waitForElement() instead of getElementById()
- Add test coverage for error codes 200, 200100, and 200xxx

These changes prevent race conditions where DOM elements are removed or not ready between checks and render calls, particularly during React/framework re-renders.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 4, 2026

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Feb 4, 2026 8:12pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 4, 2026

⚠️ No Changeset found

Latest commit: dd09d38

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

The changes modify Turnstile CAPTCHA error handling and widget initialization logic. The retry mechanism is expanded to treat error codes starting with "200" as retriable, including code "200" itself. The widget initialization flow now includes element existence checks before proceeding: it waits for the CAPTCHA container element to exist when using a smart widget with a provided container, and re-verifies the widget container element exists before rendering. If the container element is not found, the token generation promise is rejected with an error. These changes affect the turnstile.ts implementation and corresponding test expectations.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title correctly summarizes the main changes: adding retry logic and fixing race conditions for Cloudflare captcha, specifically addressing error 200100.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/clerk-js/src/utils/captcha/turnstile.ts (1)

151-219: ⚠️ Potential issue | 🟠 Major

Async Promise executor is an anti-pattern and flagged by static analysis.

The async executor can lead to unhandled promise rejections if an error is thrown before the first await. Refactor to avoid the async executor pattern.

Proposed fix
-  const handleCaptchaTokenGeneration = async (): Promise<[string, string]> => {
-    return new Promise(async (resolve, reject) => {
-      try {
-        // Re-verify element exists right before render to prevent 200100 errors
-        const containerElement = await waitForElement(widgetContainerQuerySelector);
-        if (!containerElement) {
-          reject(['Widget container element not found', undefined]);
-          return;
-        }
-
-        const id = captcha.render(widgetContainerQuerySelector, {
+  const handleCaptchaTokenGeneration = async (): Promise<[string, string]> => {
+    // Re-verify element exists right before render to prevent 200100 errors
+    const containerElement = await waitForElement(widgetContainerQuerySelector).catch(() => null);
+    if (!containerElement) {
+      throw ['Widget container element not found', undefined];
+    }
+
+    return new Promise((resolve, reject) => {
+      try {
+        const id = captcha.render(widgetContainerQuerySelector, {

This moves the async operations outside the Promise executor, keeping only the synchronous callback-based captcha.render inside.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 4, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7766

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: dd09d38

Sign up for free to 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.

1 participant