Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano
, '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): deduplicate native client startup requests by mikepitre · Pull Request #9140 · clerk/javascript · GitHub
Skip to content

fix(expo): deduplicate native client startup requests - #9140

Merged
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests
Jul 17, 2026
Merged

fix(expo): deduplicate native client startup requests#9140
mikepitre merged 3 commits into
mainfrom
mike/fix-expo-startup-requests

Conversation

@mikepitre

@mikepitremikepitre commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Expo native-client startup could trigger repeated client refreshes across the JS and native runtimes. In the captured SDK 56 launch, that produced 11 Clerk requests, including eight identical GET /v1/client calls.

The duplicate requests came from native configuration racing JS client hydration, observer callbacks echoing bootstrap and JS-originated changes, and device-token updates being followed by a second explicit refresh. This change waits for the JS Clerk instance before reading its initial token, configures native once with that token, and reconciles the two clients after both sides are ready. Native observers now seed their initial snapshot and suppress bootstrap or JS-originated echoes without dropping later native changes.

Token-cache notifications are deduplicated, client changes that occur before native is ready are coalesced, and configure failures still release the synchronization barrier. Focused tests cover initial bootstrap, token rotation, remount recovery, configure failure, and native-to-JS reconciliation.

With the SDK 56 hosted-auth example unchanged, process-cold launches on iOS and Android each produce four Clerk requests: one JS and one native request for /v1/environment, plus one JS and one native request for /v1/client.

Summary by CodeRabbit

  • Improvements

    • Reduced redundant native and JavaScript client refreshes during Expo startup and reconfiguration.
    • Improved device-token and client-state synchronization, including better handling of reloads and avoiding unnecessary updates.
    • Prevented missing or duplicate client-change notifications during initialization and sync flows.
  • New Features

    • Added an enabled option to control whether native client event syncing runs, including queuing and merging refresh actions until readiness.
  • Tests

    • Expanded hook and provider coverage to verify conditional subscription and synchronization behavior.

@changeset-bot

changeset-botBot commented Jul 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 13a8c48

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 11, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJul 17, 2026 4:25pm
swingsetReadyReadyPreview, CommentJul 17, 2026 4:25pm

Request Review

@coderabbitai

coderabbitaiBot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: fec36d8c-6117-4030-9e45-c50b35fa2cfb

📥 Commits

Reviewing files that changed from the base of the PR and between 1ffd4de and 5dcfe18.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • .changeset/calm-clients-start.md
  • packages/expo/src/hooks/tests/useNativeClientEvents.test.ts
  • packages/expo/src/provider/tests/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/provider/nativeClientSync.tsx

📝 Walkthrough

Walkthrough

Expo native and JavaScript client synchronization now gates startup on readiness, avoids redundant refreshes, suppresses duplicate client-change events, queues changes before readiness, and conditionally manages native event subscriptions.

Changes

Expo client synchronization

Layer / File(s)Summary
Native token and refresh coordination
packages/expo/android/.../ClerkExpoModule.kt, packages/expo/ios/ClerkNativeBridge.swift
Native bridges normalize tokens, compare client state before refreshing, and suppress duplicate events during configuration and JavaScript-originated sync.
JavaScript bootstrap and queued synchronization
packages/expo/src/provider/nativeClientSync.tsx
Token-cache notifications now require actual value changes; bootstrap waits for readiness, reconciles cached and native tokens, and queues refreshes until enabled.
Readiness-gated event subscriptions
packages/expo/src/hooks/useNativeClientEvents.ts, packages/expo/src/provider/ClerkProvider.tsx
Native listeners and client synchronization activate from native readiness and clean up when disabled.
Synchronization regression coverage and release metadata
packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts, packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx, .changeset/calm-clients-start.md
Tests cover listener lifecycle, readiness, queued changes, token suppression, recovery, and refresh behavior; a patch release is declared for @clerk/expo.

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

Sequence Diagram(s)

sequenceDiagram
participant ClerkProvider
participant NativeClientSync
participant ClerkInstance
participant NativeModule
ClerkProvider->>NativeClientSync: wait for native and Clerk readiness
NativeClientSync->>ClerkInstance: read cached token and status
NativeClientSync->>NativeModule: configure or synchronize token state
NativeModule-->>NativeClientSync: client state and native events
NativeClientSync->>ClerkInstance: apply queued refresh when enabled
Loading

Possibly related PRs

Poem

I’m a bunny guarding startup bright,
Fewer refreshes hop into sight.
Tokens queue till readiness gleams,
Duplicate events fade from dreams.
Native and JavaScript sync as one!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 16.67% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: deduplicating Expo native client startup requests.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 13a8c48

@mikepitremikepitre changed the title [codex] fix(expo): deduplicate native client startup requestsfix(expo): deduplicate native client startup requestsJul 11, 2026
@mikepitre
mikepitre marked this pull request as ready for review July 16, 2026 02:25
@mikepitre
mikepitre requested a review from a teamJuly 16, 2026 02:25
@github-actions

github-actionsBot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T16:26:12.637Z

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 13a8c48.

@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

Caution

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

⚠️ Outside diff range comments (1)
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt (1)

403-436: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Handle cleared device tokens in the native sync path.
syncClientStateFromJs(..., null, ..., true) still leaves the previous native device token in place on both platforms, so clearing CLERK_CLIENT_JWT_KEY can repopulate stale client state instead of clearing it.

  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt#L403-L436: add an explicit clear branch when didChangeDeviceToken is true and deviceToken is null/blank.
  • packages/expo/ios/ClerkNativeBridge.swift#L317-L329: handle the nil token path the same way.
🤖 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/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`
around lines 403 - 436, Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.

Source: Learnings

🤖 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 814-826: Update the exported hook useNativeClientBootstrap with an
explicit named return interface describing isMountedRef and isNativeClientReady,
and annotate its return type accordingly. Also annotate the exported
useNativeClientEventSync hook with : void, preserving both hooks’ existing
behavior.
---
Outside diff comments:
In `@packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt`:
- Around line 403-436: Handle cleared device tokens in syncClientStateFromJs on
both platforms: in
packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt lines
403-436, add an explicit branch for didChangeDeviceToken with a null or blank
deviceToken that clears the native device token; in
packages/expo/ios/ClerkNativeBridge.swift lines 317-329, apply the equivalent
nil-token clearing behavior. Ensure the existing non-empty token update path
remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: b1301bea-1056-418a-bc97-fe47cecfdcff

📥 Commits

Reviewing files that changed from the base of the PR and between b51e224 and 245258e.

📒 Files selected for processing (8)
  • .changeset/calm-clients-start.md
  • packages/expo/android/src/main/java/expo/modules/clerk/ClerkExpoModule.kt
  • packages/expo/ios/ClerkNativeBridge.swift
  • packages/expo/src/hooks/__tests__/useNativeClientEvents.test.ts
  • packages/expo/src/hooks/useNativeClientEvents.ts
  • packages/expo/src/provider/ClerkProvider.tsx
  • packages/expo/src/provider/__tests__/ClerkProvider.nativeClientSync.test.tsx
  • packages/expo/src/provider/nativeClientSync.tsx

Comment on lines 814 to 826
export function useNativeClientBootstrap({
publishableKey,
nativeRefreshFromJsControllerRef,
suppressTokenCacheNotificationsRef,
tokenCache,
clerkInstance,
}: {
publishableKey: string;
nativeRefreshFromJsControllerRef: MutableRefObject<NativeRefreshFromJsController | null>;
suppressTokenCacheNotificationsRef: MutableRefObject<number>;
tokenCache: TokenCache | undefined;
clerkInstance: SyncableClerkInstance | null | undefined;
}) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
# Map the file and inspect the relevant sections
wc -l packages/expo/src/provider/nativeClientSync.tsx
ast-grep outline packages/expo/src/provider/nativeClientSync.tsx --view expanded | sed -n '1,220p'printf'\n--- lines 780-980 ---\n'
sed -n '780,980p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- exported hook definitions ---\n'
rg -n "export function useNativeClient(Bootstrap|EventSync)|function useNativeClient(Bootstrap|EventSync)" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 8748


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Read only the relevant slices if the first command is not enough
sed -n '814,826p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n---\n'
sed -n '944,960p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n--- JSDoc/export context ---\n'
rg -n "useNativeClientBootstrap|useNativeClientEventSync|`@public`|`@since`|reference" packages/expo/src/provider/nativeClientSync.tsx

Repository: clerk/javascript

Length of output: 1325


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Find nearby exported hooks in this file and similar files to compare return-type style
rg -n "export function use[A-Z].*\(" packages/expo/src packages -g '*.ts' -g '*.tsx'| sed -n '1,120p'

Repository: clerk/javascript

Length of output: 14928


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- useNativeClientBootstrap body end ---'
sed -n '814,910p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- useNativeClientEventSync body end ---'
sed -n '944,1001p' packages/expo/src/provider/nativeClientSync.tsx
printf'\n%s\n''--- package export surface ---'
git ls-files packages/expo | rg '(^|/)index\.(ts|tsx|js|jsx|mjs|cjs)$|package\.json$'printf'\n%s\n''--- references to nativeClientSync.tsx ---'
rg -n "nativeClientSync\.tsx|useNativeClientBootstrap|useNativeClientEventSync|useSyncableTokenCache" packages/expo packages -g '!**/dist/**' -g '!**/build/**'

Repository: clerk/javascript

Length of output: 8177


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'%s\n''--- provider barrel(s) ---'
git ls-files packages/expo/src/provider
printf'\n%s\n''--- provider index / exports ---'forfin packages/expo/src/provider/index.ts packages/expo/src/provider/index.tsx packages/expo/src/index.ts packages/expo/src/hooks/index.ts;doif [ -f"$f" ];thenecho"### $f"
sed -n '1,220p'"$f"echofidoneprintf'%s\n''--- package entrypoints mentioning provider ---'
rg -n "provider/|ClerkProvider|useNativeClientBootstrap|useNativeClientEventSync|nativeClientSync" packages/expo/src/index.ts packages/expo/src/provider packages/expo/src/hooks -g '*.ts' -g '*.tsx'

Repository: clerk/javascript

Length of output: 9635


🏁 Script executed:

#!/bin/bashset -euo pipefail
# Check whether a result type already exists or is used nearby
rg -n "type .*Bootstrap|interface .*Bootstrap|UseNativeClientBootstrap|UseNativeClientEventSync|isNativeClientReady|isMountedRef" packages/expo/src/provider/nativeClientSync.tsx packages/expo/src/provider/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 1435


Add explicit return types to these exported hooks.useNativeClientBootstrap should use a named result interface for { isMountedRef, isNativeClientReady }, and useNativeClientEventSync should be annotated : void.

🤖 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 814 - 826,
Update the exported hook useNativeClientBootstrap with an explicit named return
interface describing isMountedRef and isNativeClientReady, and annotate its
return type accordingly. Also annotate the exported useNativeClientEventSync
hook with : void, preserving both hooks’ existing behavior.

Sources: Coding guidelines, Path instructions

@macroscopeapp

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR significantly changes the native/JS client synchronization flow during Expo startup, introducing new state tracking, conditional refresh logic, and altered bootstrap behavior across iOS, Android, and TypeScript. The complexity and sensitivity of authentication state handling warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@wobsorianowobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Awesome! The Expo E2E test passed 🙏🏼

@mikepitre
mikepitre merged commit 9599346 into mainJul 17, 2026
90 of 91 checks passed
@mikepitre
mikepitre deleted the mike/fix-expo-startup-requests branch July 17, 2026 17:02
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@mikepitre@wobsoriano