Skip to content

fix(ui): Stop the sign-in start card flashing after a code is accepted - #9331

Open
dmoerner wants to merge 2 commits into
mainfrom
fix/sign-in-start-card-flash-after-set-active
Open

fix(ui): Stop the sign-in start card flashing after a code is accepted#9331
dmoerner wants to merge 2 commits into
mainfrom
fix/sign-in-start-card-flash-after-set-active

Conversation

@dmoerner

@dmoerner dmoerner commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

After a verification code was accepted, the sign-in start card could reappear for a moment before the app rendered its signed-in state.

SignInFactorOne has a guard that sends you back to the start of the flow when the sign-in has no status left, written for "user tried a social button, then came back". #6462 gave that guard an early-return while setActive is running, and in doing so made it re-run every time setActive starts or stops rather than only on mount. So it runs once more as setActive finishes -- and at that point a successful sign-in also has no status left, because completing it consumed the sign-in. The guard can't tell the two cases apart, so it sends a user who just signed in back to the start card.

It only showed up sometimes because it needs the card to re-render both during setActive and again after it, racing whatever the app does once the session exists.

The fix records that setActive took over, so a card that has handed off navigation never sends the user back. Behaviour for a genuinely abandoned sign-in is unchanged. SignInFactorTwo has the same guard and the same bug, so it gets the same fix.

This PR was written by Claude based on a reproduction video that I was able to record. Reproduction video, at round 6.85 seconds:

sign-in-bounce.mp4

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

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

After a verification code was accepted, the sign-in start card could reappear
for a moment before the app rendered its signed-in state.

`SignInFactorOne` has a guard that sends you back to the start of the flow when
the sign-in has no status left, written for "user tried a social button, then
came back". #6462 gave that guard an early-return while `setActive` is running,
and in doing so made it re-run every time `setActive` starts or stops rather
than only on mount. So it runs once more as `setActive` finishes -- and at that
point a successful sign-in also has no status left, because completing it
consumed the sign-in. The guard can't tell the two cases apart, so it sends a
user who just signed in back to the start card.

It only showed up sometimes because it needs the card to re-render both during
`setActive` and again after it, racing whatever the app does once the session
exists.

The fix records that `setActive` took over, so a card that has handed off
navigation never sends the user back. Behaviour for a genuinely abandoned
sign-in is unchanged. `SignInFactorTwo` has the same guard and the same bug,
so it gets the same fix.

This PR was written by Claude based on a reproduction video that I was
able to record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5879680

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

This PR includes changesets to release 3 packages
Name Type
@clerk/ui Patch
@clerk/chrome-extension Patch
@clerk/swingset Patch

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

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Aug 7, 2026 5:43pm
swingset Ready Ready Preview Aug 7, 2026 5:43pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9331

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9331

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9331

@clerk/expo

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

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9331

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 5879680

@dmoerner
dmoerner marked this pull request as ready for review August 7, 2026 13:51
@dmoerner
dmoerner requested a review from alexcarpenter August 7, 2026 13:51
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-08-07T17:44:31.734Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on 5879680.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a2a5499-7bcb-4f4e-b6cb-ca69aa94dc6a

📥 Commits

Reviewing files that changed from the base of the PR and between 715a736 and 5879680.

📒 Files selected for processing (1)
  • packages/ui/src/components/SignIn/SignInFactorTwo.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/cli (auto-detected)
  • clerk/clerk-android (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/SignIn/SignInFactorTwo.tsx

📝 Walkthrough

Walkthrough

The sign-in factor effects now track when setActive takes over navigation. They skip the follow-up status redirect that can return to the start card during sign-in completion. Tests cover normal completion, signUpIfMissing transfers, and abandoned sign-ins. A patch changeset documents the fix.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • clerk/javascript#7928 — Modifies sign-in factor navigation around asynchronous setActive and sign-up transfer completion.
  • clerk/javascript#9328 — Addresses setActive-driven navigation races in sign-in factor flows.
  • clerk/javascript#9255 — Modifies authentication-flow navigation around setActive and state transitions.

Suggested reviewers: alexcarpenter, dstaley, wobsoriano

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary UI fix for the sign-in start card flashing after code acceptance.
Description check ✅ Passed The description explains the bug, identifies the affected components, describes the fix, and states that abandoned sign-in behavior remains unchanged.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx`:
- Around line 18-27: Update mockSetActiveLifecycle to use the inferred fixture
type and Clerk’s SetActiveParams instead of any, accessing
__internal_setActiveInProgress directly. Set firstFactorVerification.status
directly, and replace any-cast completion responses with typed SignInResource
and SignUpResource completion responses.

In `@packages/ui/src/components/SignIn/SignInFactorTwo.tsx`:
- Around line 32-43: Add a lifecycle regression test for SignInFactorTwo
covering activation while clerk.__internal_setActiveInProgress is true, the
subsequent signIn.status = null transition, resetting setActiveTookOverRef, and
confirming navigation to ../ does not occur. Reuse the component’s existing test
setup and navigation mocks.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 327e5405-8086-424d-9d37-9e23848d4cb9

📥 Commits

Reviewing files that changed from the base of the PR and between 438f2e5 and 715a736.

📒 Files selected for processing (4)
  • .changeset/olive-donuts-wave.md
  • packages/ui/src/components/SignIn/SignInFactorOne.tsx
  • packages/ui/src/components/SignIn/SignInFactorTwo.tsx
  • packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/cli (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment on lines +18 to +27
const mockSetActiveLifecycle = (fixtures: any) => {
let release = () => {};
const gate = new Promise<void>(resolve => (release = resolve));

fixtures.clerk.setActive.mockImplementation(async (params: any) => {
fixtures.clerk.__internal_setActiveInProgress = true;
fixtures.signIn.status = null;
await gate;
await params.navigate?.({ session: { currentTask: null }, decorateUrl: (url: string) => url });
fixtures.clerk.__internal_setActiveInProgress = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f -E node_modules '^create-fixtures\.(ts|tsx)$' . \
  -x ast-grep outline {} --items all

rg -n -C 5 \
  'bindCreateFixtures|setActive|__internal_setActiveInProgress|SignInResource' \
  packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx \
  $(fd -t f -E node_modules '^create-fixtures\.(ts|tsx)$' .)

Repository: clerk/javascript

Length of output: 18615


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test outline ---'
ast-grep outline packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx --items all

printf '%s\n' '--- fixture outline ---'
ast-grep outline packages/ui/src/test/create-fixtures.tsx --items all

printf '%s\n' '--- test source ---'
cat -n packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx

printf '%s\n' '--- fixture implementation ---'
cat -n packages/ui/src/test/create-fixtures.tsx | sed -n '1,220p'

printf '%s\n' '--- relevant declarations and internal fields ---'
rg -n -C 5 \
  'setActive\s*[:(]|__internal_setActiveInProgress|firstFactorVerification|class SignIn|interface SignIn|type SetActive|SetActive' \
  packages packages/shared 2>/dev/null | head -n 500

Repository: clerk/javascript

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- fixture helper and mock types ---'
fd -t f -E node_modules 'mock-helpers\.(ts|tsx)$|fixture-helpers\.(ts|tsx)$' packages/ui/src packages/clerk-js/src \
  -x sh -c 'echo "--- $1"; cat -n "$1"' sh {}

printf '%s\n' '--- Clerk setActive declarations and implementation ---'
rg -l 'setActive|__internal_setActiveInProgress' packages/clerk-js packages/shared/src \
  -g '*.{ts,tsx}' | sort | while read -r file; do
  echo "--- $file"
  rg -n -C 8 'setActive|__internal_setActiveInProgress' "$file" | head -n 180
done

printf '%s\n' '--- SignInResource relevant fields ---'
sed -n '35,180p' packages/shared/src/types/signIn.ts
rg -n -C 6 'firstFactorVerification' packages/clerk-js packages/shared/src packages/ui/src -g '*.{ts,tsx}' | head -n 240

Repository: clerk/javascript

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- LoadedClerk and setActive type locations ---'
rg -n 'interface LoadedClerk|type LoadedClerk|setActive\s*\??:|setActive\(' \
  packages/shared/src/types packages/clerk-js/src -g '*.{ts,tsx}' | head -n 160

printf '%s\n' '--- exact setActive declarations ---'
rg -l 'setActive' packages/shared/src/types packages/clerk-js/src -g '*.{ts,tsx}' | while read -r file; do
  matches=$(rg -n 'setActive' "$file" | head -n 12)
  if [ -n "$matches" ]; then
    echo "--- $file"
    printf '%s\n' "$matches"
  fi
done

printf '%s\n' '--- exact sign-in response and verification types ---'
rg -n -C 5 \
  'AttemptFirstFactor|FirstFactorVerification|firstFactorVerification|first_factor_verification|createdSessionId|created_session_id' \
  packages/shared/src/types/signIn.ts packages/shared/src/types -g '*.ts' | head -n 260

printf '%s\n' '--- internal set-active state references ---'
rg -n -C 6 '__internal_setActiveInProgress' packages -g '*.{ts,tsx}' || true

Repository: clerk/javascript

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- LoadedClerk internal field and setActive contract ---'
sed -n '900,930p;1208,1230p;1660,1730p' packages/shared/src/types/clerk.ts

printf '%s\n' '--- SignInResource and status types ---'
sed -n '1,95p' packages/shared/src/types/signIn.ts
rg -n 'export type SignInStatus|type SignInStatus|interface VerificationResource|status:' \
  packages/shared/src/types/signIn.ts packages/shared/src/types/verification.ts packages/shared/src/types -g '*.ts' | head -n 120

printf '%s\n' '--- SignUpResource completion contract ---'
rg -n -C 12 'export interface SignUpResource|createdSessionId|status:' \
  packages/shared/src/types/signUp.ts | head -n 180

printf '%s\n' '--- Deep mock and fixture return declarations ---'
sed -n '1,100p' packages/ui/src/test/mock-helpers.ts
sed -n '38,150p' packages/ui/src/test/create-fixtures.tsx

Repository: clerk/javascript

Length of output: 22855


Replace any with the existing Clerk types.

Use the inferred fixture type and SetActiveParams contract. Access __internal_setActiveInProgress directly. Update firstFactorVerification.status directly. Use typed SignInResource and SignUpResource completion responses instead of as any casts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/SignIn/__tests__/SignInFactorOneSetActiveGuard.test.tsx`
around lines 18 - 27, Update mockSetActiveLifecycle to use the inferred fixture
type and Clerk’s SetActiveParams instead of any, accessing
__internal_setActiveInProgress directly. Set firstFactorVerification.status
directly, and replace any-cast completion responses with typed SignInResource
and SignUpResource completion responses.

Source: Coding guidelines

Comment on lines +32 to 43
const setActiveTookOverRef = React.useRef(false);

React.useEffect(() => {
if (clerk.__internal_setActiveInProgress) {
// setActive owns navigation from here on. It consumes the sign-in (status -> null), so the
// check below would fire as setActive winds down and redirect over a flow that succeeded.
setActiveTookOverRef.current = true;
return;
}

if (setActiveTookOverRef.current) {
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline packages/ui/src/components/SignIn/SignInFactorTwo.tsx --items all

rg -n -C 5 \
  'SignInFactorTwo|__internal_setActiveInProgress|setActive|navigate\(' \
  packages/ui/src/components/SignIn \
  --glob '*.{test,spec}.{ts,tsx}' || true

Repository: clerk/javascript

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- SignInFactorTwo.tsx ---'
sed -n '1,155p' packages/ui/src/components/SignIn/SignInFactorTwo.tsx

printf '%s\n' '--- SignInFactorTwo tests ---'
sed -n '1,220p' packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx

printf '%s\n' '--- lifecycle-specific test names and assertions ---'
rg -n -i \
  'setActive|__internal_setActiveInProgress|status\s*=\s*null|navigate.*\.\./|not\.toHaveBeenCalledWith.*\.\./' \
  packages/ui/src/components/SignIn/__tests__/SignInFactorTwo*.{test,spec}.{ts,tsx} \
  2>/dev/null || true

Repository: clerk/javascript

Length of output: 15356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

component = Path("packages/ui/src/components/SignIn/SignInFactorTwo.tsx").read_text()
test_files = list(Path("packages/ui/src/components/SignIn/__tests__").glob("SignInFactorTwo*.test.tsx"))
tests = "\n".join(p.read_text() for p in test_files)

required = {
    "imports SignInFactorTwo": bool(re.search(r"import\s+\{\s*SignInFactorTwo\s*\}", tests)),
    "sets setActive progress": "__internal_setActiveInProgress" in tests,
    "sets signIn status null": bool(re.search(r"signIn\.status\s*=\s*null", tests)),
    "resets progress flag": bool(re.search(r"__internal_setActiveInProgress\s*=\s*false", tests)),
    "asserts no ../ navigation": bool(re.search(r"not\.toHaveBeenCalledWith\(\s*['\"]\.\./['\"]", tests)),
}
print("component has takeover guard:", "__internal_setActiveInProgress" in component)
for name, present in required.items():
    print(f"{name}: {present}")
PY

Repository: clerk/javascript

Length of output: 342


Add a SignInFactorTwo lifecycle regression test.

SignInFactorTwo.test.tsx does not cover the setActive flag lifecycle. Test activation, signIn.status = null, flag reset, and no navigation to ../.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/SignIn/SignInFactorTwo.tsx` around lines 32 - 43,
Add a lifecycle regression test for SignInFactorTwo covering activation while
clerk.__internal_setActiveInProgress is true, the subsequent signIn.status =
null transition, resetting setActiveTookOverRef, and confirming navigation to
../ does not occur. Reuse the component’s existing test setup and navigation
mocks.

Source: Coding guidelines

@alexcarpenter
alexcarpenter requested a review from dstaley August 7, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants