Skip to content

fix(expo): Prevent stale native clients from replacing signed-in sessions#9222

Merged
wobsoriano merged 5 commits into
mainfrom
rob/fix-js-issue-9217
Jul 24, 2026
Merged

fix(expo): Prevent stale native clients from replacing signed-in sessions#9222
wobsoriano merged 5 commits into
mainfrom
rob/fix-js-issue-9217

Conversation

@wobsoriano

Copy link
Copy Markdown
Member

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:

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: da2894c

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

This PR includes changesets to release 1 package
Name Type
@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 22, 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, Comment Jul 24, 2026 6:10pm
swingset Ready Ready Preview, Comment Jul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-google-signin

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: da2894c

@mikepitre mikepitre 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.

Really nice work tracking this down — I went through the FAPI side to sanity-check the assumptions and the foreign-sessionless rejection holds up well against what the server actually does (a stale token on GET /v1/client re-mints a lazy client reusing the same id, so the validation fetch is deterministic). I found one interaction with the 401 recovery path that I think can still reproduce the original bug, plus two smaller things — details inline.

Comment thread packages/expo/src/provider/nativeClientSync.tsx
Comment thread packages/expo/src/provider/nativeClientSync.tsx
Comment thread packages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsoriano wobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess… fix(expo): Prevent stale native clients from replacing signed-in sessions Jul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Expo native client synchronization now distinguishes token-cache timeouts from confirmed missing tokens, validates refreshed clients against the current JS client, restores prior device tokens when native verification fails, and rejects foreign sessionless clients. Recovery and bootstrap paths use the updated logic. The Expo fixture adds Android token-divergence controls and an end-to-end regression flow, with provider tests covering the synchronization scenarios.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • clerk/javascript#9140 — Modifies the same Expo native client synchronization and token-cache coordination paths.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is mostly boilerplate and doesn't describe the actual changes, so it is too generic to assess fully. Replace the template text with a brief summary of the Expo native sync fix and how to test it.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
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.

Warning

Review ran into problems

🔥 Problems

Linked repositories: Couldn't analyze clerk/clerk-android - clone failed: Clone operation failed: Cloning into '/home/jailuser/git'...
warning: templates not found in /usr/share/git-core/templates
From https://github.com/clerk/clerk-android

Errors logged to '/home/jailuser/git/.git/lfs/logs/20260724T181338.342058045.log'.
Use git lfs logs last to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: config/bin/detekt-cli: smudge filter lfs failed
Downloading config/bin/detekt-cli (71 MB)
Error downloading object: config/bin/detekt-cli (2655f48): Smudge error: Error downloading config/bin/detekt-cli (2655f48c7c303a5f9bf920a33229408b8571bb1d29e4e57cf7be3e151bceecb1): LFS: Client error: https://github-cloud.githubusercontent.com/alambic/media/877189286/26/55/2655f48c7c303a5f9bf920a33229408b8571bb1d29e4e57cf7be3e151bceecb1?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA5BA2674WPWWEFGQ5%2F20260724%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260724T181458Z&X-Amz-Expires=3600&X-Amz-Signature=cb976f8a599f731c508e7916425211e39c1fd46e4451026ee07a149bf623497c&X-Amz-SignedHeaders=host&actor_id=136622811&key_id=0&repo_id=980409545&token=1

Errors logged to '/home/jailuser/git/.git/lfs/logs/20260724T181458.65044682.log'.
Use git lfs logs last to view the log.
error: external filter 'git-lfs filter-process' failed
fatal: config/bin/detekt-cli: smudge filter lfs failed


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

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-24T18:11:07.908Z

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 da2894c.

@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/expo/src/provider/nativeClientSync.tsx`:
- Around line 932-934: Update the bootstrap cache reads around
initialJsDeviceToken and the corresponding second read to preserve the timeout
sentinel returned by getCachedDeviceToken instead of coercing it to null. Ensure
syncClientStateFromJs is invoked only when the cache result is authoritative,
while retaining null for a confirmed missing token.
🪄 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), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 658231b0-2756-4fda-b111-cbbeaa9c707b

📥 Commits

Reviewing files that changed from the base of the PR and between 41c323a and f069ecd.

📒 Files selected for processing (3)
  • .changeset/protect-expo-native-client-sync.md
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.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/cli (auto-detected)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)

Comment on lines 932 to +934
let initialJsDeviceToken: string | null = null;
try {
initialJsDeviceToken = await getCachedDeviceToken(tokenCache);
initialJsDeviceToken = (await getCachedDeviceToken(tokenCache)) ?? null;

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant file around the bootstrap logic and the native sync call.
sed -n '900,980p' packages/expo/src/provider/nativeClientSync.tsx
printf '\n---\n'
sed -n '1,260p' packages/expo/src/provider/nativeClientSync.tsx

# Find the implementation and call sites of ClerkExpo.syncClientStateFromJs.
printf '\n=== syncClientStateFromJs references ===\n'
rg -n "syncClientStateFromJs" packages/expo packages -g '!**/dist/**' -g '!**/build/**' || true

# Inspect the native bridge / implementation if present.
printf '\n=== ClerkExpo implementation candidates ===\n'
fd -H "ClerkExpo" packages/expo packages -t f || true

Repository: clerk/javascript

Length of output: 29941


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== iOS module ===\n'
sed -n '80,180p' packages/expo/ios/ClerkExpoModule.swift

printf '\n=== iOS native bridge ===\n'
sed -n '291,380p' packages/expo/ios/ClerkNativeBridge.swift

printf '\n=== Android module ===\n'
sed -n '387,470p' packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt

printf '\n=== Native module type signature ===\n'
sed -n '1,80p' packages/expo/src/specs/NativeClerkModule.ts
sed -n '1,80p' packages/expo/src/specs/NativeClerkModule.android.ts

printf '\n=== Bootstrap-related tests around null device token ===\n'
sed -n '280,330p' packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
sed -n '1110,1210p' packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx

Repository: clerk/javascript

Length of output: 13840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== getCachedDeviceToken helper ===\n'
rg -n "function getCachedDeviceToken|getCachedDeviceToken\\(" packages/expo/src/provider/nativeClientSync.tsx
sed -n '560,700p' packages/expo/src/provider/nativeClientSync.tsx

printf '\n=== bootstrap branch around currentJsDeviceToken ===\n'
sed -n '940,980p' packages/expo/src/provider/nativeClientSync.tsx

printf '\n=== tests covering timeout / null cache behavior ===\n'
rg -n "timeout|undefined|null" packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx

Repository: clerk/javascript

Length of output: 11901


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '420,470p' packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1739


Preserve the timeout sentinel in bootstrap
Both bootstrap reads collapse a timed-out cache lookup back to null, so a slow second read becomes indistinguishable from a confirmed missing token. That can send bootstrap down the JS→native sync path on an unreliable snapshot. syncClientStateFromJs(null, ..., true, true) doesn’t clear native state, but it still forces a refresh that should be skipped until the cache read is authoritative.

🤖 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/expo/src/provider/nativeClientSync.tsx` around lines 932 - 934,
Update the bootstrap cache reads around initialJsDeviceToken and the
corresponding second read to preserve the timeout sentinel returned by
getCachedDeviceToken instead of coercing it to null. Ensure
syncClientStateFromJs is invoked only when the cache result is authoritative,
while retaining null for a confirmed missing token.

@mikepitre mikepitre 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.

@wobsoriano I didn't test manually but if all looks good with your manual testing then the code itself looks good to me!

@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.

🧹 Nitpick comments (1)
integration/templates/expo-native/components/E2EControls.tsx (1)

11-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add explicit public function return types.

The new exported components omit explicit return types; GoogleSignInButton also lacks public API documentation.

  • integration/templates/expo-native/components/E2EControls.tsx#L11-L33: add the component and async-handler return types.
  • integration/templates/expo-native/components/GoogleSignInButton.tsx#L5-L24: add JSDoc and the component return type.
  • integration/templates/expo-native/components/JsSignInForm.tsx#L10-L31: add the component and helper return types.
🤖 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 `@integration/templates/expo-native/components/E2EControls.tsx` around lines 11
- 33, Add explicit public return types to E2EControls and its async handlers
onCorruptNativeToken and onMintSessionToken in
integration/templates/expo-native/components/E2EControls.tsx; document and type
GoogleSignInButton in
integration/templates/expo-native/components/GoogleSignInButton.tsx; and type
JsSignInForm plus its helper functions in
integration/templates/expo-native/components/JsSignInForm.tsx. Use return types
matching each function’s existing behavior and add the requested JSDoc for
GoogleSignInButton.

Source: Coding guidelines

🤖 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.

Nitpick comments:
In `@integration/templates/expo-native/components/E2EControls.tsx`:
- Around line 11-33: Add explicit public return types to E2EControls and its
async handlers onCorruptNativeToken and onMintSessionToken in
integration/templates/expo-native/components/E2EControls.tsx; document and type
GoogleSignInButton in
integration/templates/expo-native/components/GoogleSignInButton.tsx; and type
JsSignInForm plus its helper functions in
integration/templates/expo-native/components/JsSignInForm.tsx. Use return types
matching each function’s existing behavior and add the requested JSDoc for
GoogleSignInButton.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 74277d0f-f190-4562-9ee7-29c83dcf2bef

📥 Commits

Reviewing files that changed from the base of the PR and between f069ecd and da2894c.

📒 Files selected for processing (8)
  • integration/templates/expo-native/App.tsx
  • integration/templates/expo-native/components/E2EControls.tsx
  • integration/templates/expo-native/components/GoogleSignInButton.tsx
  • integration/templates/expo-native/components/JsSignInForm.tsx
  • integration/templates/expo-native/modules/e2e-hooks/android/build.gradle
  • integration/templates/expo-native/modules/e2e-hooks/android/src/main/java/com/clerk/e2ehooks/E2EHooksModule.kt
  • integration/templates/expo-native/modules/e2e-hooks/expo-module.config.json
  • integration/tests/expo-native/flows/session-survives-native-token-divergence.yaml
🔗 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)

@wobsoriano
wobsoriano merged commit 6c9ce3f into main Jul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants