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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', '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('^' + ".*" + ' fix(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, '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" + ' fix(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, '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('^' + ".*" + ' fix(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre
, '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(expo): Prevent stale native clients from replacing signed-in sessions by wobsoriano · Pull Request #9222 · clerk/javascript · GitHub
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-botBot 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
NameType
@clerk/expoPatch

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 Jul 22, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 24, 2026 6:10pm
swingsetReadyReadyPreview, CommentJul 24, 2026 6:10pm

Request Review

@pkg-pr-new

pkg-pr-newBot 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

@mikepitremikepitre 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 threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx
Comment threadpackages/expo/src/provider/nativeClientSync.tsx Outdated
@wobsorianowobsoriano changed the title fix(expo): Prevent stale native clients from replacing signed-in sess…fix(expo): Prevent stale native clients from replacing signed-in sessionsJul 24, 2026
@wobsoriano
wobsoriano marked this pull request as ready for review July 24, 2026 17:05
@coderabbitai

coderabbitaiBot 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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❓ InconclusiveThe 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 nameStatusExplanation
Title check✅ PassedThe title matches the main change: preventing stale native Expo clients from replacing signed-in sessions.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck 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-actionsBot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

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

Summary

MetricCount
Packages analyzed19
Packages with changes0
🔴 Breaking changes0
🟡 Non-breaking changes0
🟢 Additions0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on da2894c.

@coderabbitaicoderabbitaiBot 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/bashset -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/bashset -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/bashset -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/bashset -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.

@mikepitremikepitre 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!

@coderabbitaicoderabbitaiBot 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 mainJul 24, 2026
59 checks passed
@wobsoriano
wobsoriano deleted the rob/fix-js-issue-9217 branch July 24, 2026 18:27
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.

2 participants

@wobsoriano@mikepitre