Skip to content

fix(js): preserve SignInFuture reference after resetSignIn#9071

Open
FrancoKaddour wants to merge 1 commit into
clerk:mainfrom
FrancoKaddour:fix/signin-future-stale-ref-after-reset
Open

fix(js): preserve SignInFuture reference after resetSignIn#9071
FrancoKaddour wants to merge 1 commit into
clerk:mainfrom
FrancoKaddour:fix/signin-future-stale-ref-after-reset

Conversation

@FrancoKaddour

@FrancoKaddour FrancoKaddour commented Jul 2, 2026

Copy link
Copy Markdown

After resetSignIn(), the next call to fromJSON with fresh sign-in data was creating a brand-new SignIn instance because the id comparison this.signIn.id === data.sign_in.id) failed — the current sign-in had no id after the reset. This caused useSignIn() hooks to hold a stale __internal_future reference pointing at the discarded instance.

Fix: add || !this.signIn.id to the condition so an empty-id SignIn is updated in-place instead of replaced, preserving the __internal_future reference across the reset → new attempt transition. Same pattern as the existing SignUp fix.

Closes #9006

Summary by CodeRabbit

  • Bug Fixes
    • Fixed sign-in state so a reset no longer leaves sign-in hooks with a stale reference.
    • Subsequent sign-in attempts now update the existing sign-in state instead of swapping it out, keeping the active flow in sync.
  • Tests
    • Added coverage to verify sign-in state updates correctly after a reset.

After resetSignIn(), fromJSON was creating a new SignIn instance when the
next attempt arrived (ids didn't match), breaking the __internal_future
reference held by useSignIn() hooks. Add `|| !this.signIn.id` so an
empty-id SignIn is updated in-place instead, same pattern as SignUp.

Fixes clerk#9006
@changeset-bot

changeset-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9bbd637

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

This PR includes changesets to release 4 packages
Name Type
@clerk/clerk-js Patch
@clerk/chrome-extension Patch
@clerk/electron Patch
@clerk/expo 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 Jul 2, 2026

Copy link
Copy Markdown

@FrancoKaddour is attempting to deploy a commit to the Clerk Production Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR modifies Client.fromJSON to update the existing signIn instance in place when the instance has no id, in addition to the prior matching-ID case, preventing replacement with a new object reference. A test and changeset accompany the fix.

Changes

SignIn Instance Reuse Fix

Layer / File(s) Summary
Reuse existing signIn instance after reset
packages/clerk-js/src/core/resources/Client.ts, packages/clerk-js/src/core/resources/__tests__/Client.test.ts, .changeset/fix-signin-future-stale-ref-after-reset.md
fromJSON now updates the existing signIn instance when incoming IDs match or when this.signIn has no id, avoiding replacement; a test confirms the same instance is reused and updated after resetSignIn(), and a changeset documents the patch.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Hook as useSignIn()
  participant Client as Client
  participant SignIn as SignIn instance

  Hook->>Client: resetSignIn()
  Client->>SignIn: clear id (id becomes undefined)
  Hook->>Client: fromJSON(data with sign_in)
  alt id matches or this.signIn has no id
    Client->>SignIn: __internal_updateFromJSON(data.sign_in)
    SignIn-->>Hook: same instance updated (id, identifier)
  else
    Client->>Client: replace this.signIn with new SignIn
  end
Loading

Poem

A stale old sign-in, lost in the fray,
No more replaced, it's here to stay! 🐇
Reset the id, then patch it anew,
Same little instance, still holds true.
Hooray for the hook, no stale refs in view! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main fix: preserving the SignInFuture reference after resetSignIn.
Linked Issues check ✅ Passed The code now reuses the existing SignIn instance after reset, matching the issue's requirement for hooks to keep a live post-reset resource.
Out of Scope Changes check ✅ Passed The changes are limited to the fix, its test, and a changeset note, so nothing appears out of scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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: 1

🤖 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/clerk-js/src/core/resources/Client.ts`:
- Line 152: The SignUp reuse logic in Client.fromJSON is still too strict and
replaces the existing SignUp instance after resetSignUp(), which drops the
__internal_future used by useSignUp(). Update the sign_up branch to mirror the
SignIn handling by reusing this.signUp when the ids match or when this.signUp.id
is falsy, using the existing SignUp and fromJSON paths. Add a test covering
resetSignUp() followed by a new attempt id to ensure the same SignUp instance is
preserved and its internal future remains intact.
🪄 Autofix (Beta)

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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 91a92164-23cb-4afb-9463-f5b2fc0eeb2e

📥 Commits

Reviewing files that changed from the base of the PR and between 04bbfe9 and 9bbd637.

📒 Files selected for processing (3)
  • .changeset/fix-signin-future-stale-ref-after-reset.md
  • packages/clerk-js/src/core/resources/Client.ts
  • packages/clerk-js/src/core/resources/__tests__/Client.test.ts

}

if (data.sign_in && this.signIn instanceof SignIn && this.signIn.id === data.sign_in.id) {
if (data.sign_in && this.signIn instanceof SignIn && (this.signIn.id === data.sign_in.id || !this.signIn.id)) {

@coderabbitai coderabbitai Bot Jul 2, 2026

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
# Check if resetSignUp + fromJSON exhibits the same stale-instance issue as resetSignIn did
rg -n -A5 'resetSignUp' packages/clerk-js/src/core/resources/Client.ts
rg -n 'this.signUp.id ===' packages/clerk-js/src/core/resources/Client.ts

Repository: clerk/javascript

Length of output: 552


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' packages/clerk-js/src/core/resources/Client.ts

Repository: clerk/javascript

Length of output: 6391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -A8 -B4 'class SignUp|class SignIn|__internal_updateFromJSON|useSignUp|useSignIn' packages/clerk-js/src/core/resources packages/clerk-js/src -g '!**/dist/**'

Repository: clerk/javascript

Length of output: 28599


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' packages/clerk-js/src/core/resources/SignUp.ts
sed -n '500,760p' packages/clerk-js/src/core/resources/SignUp.ts
sed -n '200,280p' packages/clerk-js/src/core/resources/__tests__/Client.test.ts

Repository: clerk/javascript

Length of output: 19406


Align SignUp reuse with the SignIn fix

sign_up still only reuses the existing instance when ids match. After resetSignUp(), this.signUp.id is falsy, so the next fromJSON() with a new attempt id replaces the instance and drops the __internal_future reference that useSignUp() depends on. Add the same || !this.signUp.id guard here, plus a reset→new-attempt test.

🤖 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/clerk-js/src/core/resources/Client.ts` at line 152, The SignUp reuse
logic in Client.fromJSON is still too strict and replaces the existing SignUp
instance after resetSignUp(), which drops the __internal_future used by
useSignUp(). Update the sign_up branch to mirror the SignIn handling by reusing
this.signUp when the ids match or when this.signUp.id is falsy, using the
existing SignUp and fromJSON paths. Add a test covering resetSignUp() followed
by a new attempt id to ensure the same SignUp instance is preserved and its
internal future remains intact.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. The signUp side is already covered by #9060 which adds the same || !this.signUp.id guard for that path — once that lands both flows will be protected.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 9060 --repo clerk/javascript --json title,body,state,url 2>&1 | head -100

Length of output: 4839


@FrancoKaddour Confirmed — I checked #9060 and it does add the identical || !this.signUp.id guard to the sign_up branch in Client.fromJSON, addressing the same stale SignUpFuture issue for useSignUp(). Once that PR merges, both the signIn and signUp reuse paths will be consistent. No action needed here; I'll leave this as a non-blocking note for tracking purposes.

(っ˘̩╭╮˘̩)っ


🧬 Code Graph Analysis Results

packages/clerk-js/src/core/resources/Client.ts

Lines 31-33 (sign-up/sign-in attempt instances live on Client)

  sessions: Session[] = [];
  signUp: SignUpResource = new SignUp();
  signIn: SignInResource = new SignIn();

Lines 141-156 (fromJSON in-place update logic; sign-in already uses the || !this.signIn.id guard, sign-up does not in this file)

  fromJSON(data: ClientJSON | ClientJSONSnapshot | null): this {
    if (data) {
      this.id = data.id;
      this.sessions = (data.sessions || []).map(s => new Session(s));

      if (data.sign_up && this.signUp instanceof SignUp && this.signUp.id === data.sign_up.id) {
        this.signUp.__internal_updateFromJSON(data.sign_up);
      } else {
        this.signUp = new SignUp(data.sign_up);
      }

      if (data.sign_in && this.signIn instanceof SignIn && (this.signIn.id === data.sign_in.id || !this.signIn.id)) {
        this.signIn.__internal_updateFromJSON(data.sign_in);
      } else {
        this.signIn = new SignIn(data.sign_in);
      }

      this.lastActiveSessionId = data.last_active_session_id;
      this.captchaBypass = data.captcha_bypass || false;
      this.cookieExpiresAt = data.cookie_expires_at ? unixEpochToDate(data.cookie_expires_at) : null;
      this.lastAuthenticationStrategy = data.last_authentication_strategy || null;
      this.createdAt = unixEpochToDate(data.created_at || undefined);
      this.updatedAt = unixEpochToDate(data.updated_at || undefined);
    }

    return this;
  }

packages/clerk-js/src/core/resources/SignUp.ts

Line-range: 77-610 (relevant behavior: SignUp tracks id and populates it from API responses; this is what the Client.fromJSON check relies on)

export class SignUp extends BaseResource implements SignUpResource {
  pathRoot = '/client/sign_ups';

  id: string | undefined;
  ...
  constructor(data: SignUpJSON | SignUpJSONSnapshot | null = null) {
    super();
    this.fromJSON(data);
  }
  ...
  protected fromJSON(data: SignUpJSON | SignUpJSONSnapshot | null): this {
    if (data) {
      this.id = data.id;
      ...
    }

    eventBus.emit('resource:update', { resource: this });
    return this;
  }
  ...
}

Docstring-style summary of the relevant parts (from the same snippet range):

/**
 * SignUpResource identity:
 * - `id: string | undefined` (set from API payloads).
 * - `constructor(data)` calls `fromJSON(data)`.
 * - `fromJSON(...)` sets `this.id = data.id` when `data` is present and emits `resource:update`.
 *
 * Parameters:
 * - data: SignUpJSON | SignUpJSONSnapshot | null
 *
 * Returns:
 * - this (SignUp)
 *
 * Exceptions:
 * - none shown in the provided excerpt.
 */

🧠 Learnings used
Learnt from: wobsoriano
Repo: clerk/javascript PR: 7883
File: packages/backend/src/api/__tests__/M2MTokenApi.test.ts:414-419
Timestamp: 2026-02-24T18:03:27.067Z
Learning: In TypeScript files within the clerk/javascript repository, enforce explicit return type annotations for exported functions and public APIs, but allow internal test helper functions without explicit return types. This helps maintain API clarity and type safety for consumers while not burdening test helpers that are not part of the public surface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant