diff --git a/.changeset/fair-expo-kotlin-fixture.md b/.changeset/fair-expo-kotlin-fixture.md
new file mode 100644
index 00000000000..a845151cc84
--- /dev/null
+++ b/.changeset/fair-expo-kotlin-fixture.md
@@ -0,0 +1,2 @@
+---
+---
diff --git a/integration/templates/expo-kotlin-alignment/.gitignore b/integration/templates/expo-kotlin-alignment/.gitignore
new file mode 100644
index 00000000000..c31beb8f218
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/.gitignore
@@ -0,0 +1,6 @@
+/.expo/
+/.env*
+/android/
+/ios/
+/node_modules/
+/pnpm-lock.yaml
diff --git a/integration/templates/expo-kotlin-alignment/App.js b/integration/templates/expo-kotlin-alignment/App.js
new file mode 100644
index 00000000000..a8ff51ae5ac
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/App.js
@@ -0,0 +1,75 @@
+import React, { useState } from 'react';
+import { Button, Text, View } from 'react-native';
+import { ClerkProvider, useAuth, useClerk } from '@clerk/expo';
+import { AuthView, UserProfileView } from '@clerk/expo/native';
+import { tokenCache } from '@clerk/expo/token-cache';
+
+function Probe() {
+ const { isLoaded, isSignedIn, getToken } = useAuth();
+ const { signOut } = useClerk();
+ const [message, setMessage] = useState('');
+ const [profile, setProfile] = useState(false);
+ const [mounted, setMounted] = useState(true);
+
+ async function checkToken() {
+ try {
+ setMessage((await getToken()) ? 'Session token obtained' : 'No session token');
+ } catch (error) {
+ setMessage(error.message);
+ }
+ }
+
+ return (
+
+ Clerk Kotlin alignment probe
+
+ Loaded: {String(isLoaded)}; signed in: {String(isSignedIn)}
+
+ {message}
+
+ );
+}
+
+export default function App() {
+ const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY;
+ if (!publishableKey?.startsWith('pk_test_')) {
+ return (
+
+ Set a development EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY and rebuild.
+
+ );
+ }
+ return (
+
+
+
+ );
+}
diff --git a/integration/templates/expo-kotlin-alignment/README.md b/integration/templates/expo-kotlin-alignment/README.md
new file mode 100644
index 00000000000..95e57fb7a3f
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/README.md
@@ -0,0 +1,128 @@
+# Expo 58 Kotlin alignment experiment
+
+This isolated Android fixture explores a fully aligned Kotlin toolchain without
+changing Clerk's shipped defaults. It is pinned to published `@clerk/expo@4.7.1`,
+not the current monorepo package, so the experiment remains independent of changes
+to Clerk's native dependencies and package split.
+
+It complements [#9791](https://github.com/clerk/javascript/pull/9791), which addresses
+the immediate release-build failure, and
+[#9955](https://github.com/clerk/javascript/pull/9955), which makes native components
+optional. [#9974](https://github.com/clerk/javascript/pull/9974) separately enables
+optimization in the existing Android integration workflow.
+
+## Configuration
+
+| Input | Version |
+| ---------------------------------- | ------------------------------------------- |
+| Expo | 58.0.0-preview.8 |
+| React Native | 0.88.0-rc.2 |
+| React / React DOM | 19.3.0 |
+| Clerk Expo | 4.7.1 |
+| Clerk native API / UI | 1.1.8, selected by the pinned Clerk package |
+| Kotlin / resolved Compose compiler | 2.4.20 |
+| SavedState Compose | 1.3.3 |
+
+The official `expo-build-properties` plugin selects Kotlin 2.4.20 and enables
+minification and resource shrinking. The fixture-local pnpm patch adds SavedState
+Compose, removes six stdlib exclusions, and removes the local, cross-project and
+config-plugin metadata-check bypasses. It does not add a `dontwarn` rule.
+
+The dependency reports from the original experiment resolved both the Kotlin
+compiler and Compose compiler to 2.4.20 even though Clerk's plugin declaration still
+says 2.1.20. No additional Compose patch was necessary on this exact stack.
+
+Only this fixture consumes the patch. It is not an installation recommendation for
+older Expo SDKs and does not change Clerk's declared Expo support range. The
+expected Expo peer-range warning is retained rather than hidden.
+
+## Run
+
+Use Node 24.15 or newer, pnpm, JDK 17 and an Android SDK. Start an arm64 emulator;
+this fixture intentionally builds only `arm64-v8a` to match the original test.
+
+From this directory:
+
+```sh
+pnpm install
+```
+
+Create an ignored `.env` with your own development-instance publishable key:
+
+```dotenv
+EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_REPLACE_WITH_YOUR_DEVELOPMENT_KEY
+```
+
+```sh
+pnpm android --device YOUR_EMULATOR_NAME
+```
+
+The publication fixture also passed a fresh pnpm install and optimized build.
+Expo CLI subsequently tried to open a development-client URI that this standalone
+release does not handle; the build and installation succeeded. If that occurs,
+open the installed app from the emulator launcher.
+
+Expo manages prebuild and compilation. After changing native configuration, run
+`pnpm prebuild:android` before building again. The fixture refuses a production
+publishable key. No secret key or test credentials belong in this directory.
+
+Confirm that `android/app/build/outputs/mapping/release/mapping.txt` is nonempty.
+Inspect the generated merged R8 configuration to confirm no
+`-dontwarn kotlin.MustUseReturnValues` rule was added, and inspect the patched
+Clerk Gradle/plugin files and generated app Gradle file for absent
+`-Xskip-metadata-version-check` flags. Dependency reports should confirm the actual
+compiler artifacts, not only the configuration property.
+
+## Manual authentication checks
+
+Use an account in the development instance:
+
+1. Sign in through the native form. Complete any verification challenge.
+2. Select **Check session token**; the app reports presence, never the token.
+3. Force-stop and reopen the app; check that the session and token remain available.
+4. Open the profile, unmount/remount it, and confirm it renders again.
+5. Sign out and confirm the signed-out form returns.
+
+The original isolated alignment build passed these checks on an API 36 arm64
+emulator. Independent development Backend API reads confirmed the session changed
+from active to removed. The development test account was deleted afterwards.
+The publication fixture removes the original probe's test-user deletion control;
+manage any account cleanup separately in your development instance.
+
+## Comparison evidence
+
+These are isolated reproduction results against published Clerk 4.7.1, not a full
+execution of #9791 or a certification of all patch releases and auth providers.
+Both small-fix variants kept minification and resource shrinking enabled.
+
+| Expo | React Native | Stock | Remove exclusions | Exact annotation rule |
+| ---------------- | ------------ | ------------------------------------------- | ----------------- | --------------------- |
+| 54.0.37 | 0.81.5 | Missing annotation | Build/auth pass | Build/auth pass |
+| 55.0.31 | 0.83.10 | Missing annotation | Build/auth pass | Build/auth pass |
+| 56.0.23 | 0.85.3 | Missing annotation | Build/auth pass | Build/auth pass |
+| 57.0.25 | 0.86.3 | Missing annotation | Build/auth pass | Build/auth pass |
+| 58.0.0-preview.8 | 0.88.0-rc.2 | SavedState failure, then missing annotation | Build/auth pass | Build/auth pass |
+
+The Expo 54 narrow/runtime checks used native Clerk 1.1.9; the 55–58 checks used
+1.1.8. On 58, both small fixes included SavedState Compose 1.3.3; the 1.4.0 addition
+from #9791 was separately build/startup-tested on 57. The 58 consumer-rule build
+produced identical DEX to the authenticated app-level-rule APK, and merged R8
+configuration confirmed delivery from Clerk's library.
+
+Older SDKs emitted metadata warnings with both small fixes. The 55 narrow build
+completed R8 with 353 warnings before a disk-full interruption during lint; its
+successful unchanged retry reused R8 outputs and must not be called warning-free.
+The fully aligned 58 experiment had no R8 metadata warnings.
+
+## Boundaries and next decision
+
+The native publication floor, older Expo compiler constraints and supported
+version policy still need maintainer direction. This fixture is deliberately not
+a conditional production implementation based on SDK version.
+
+Physical devices, Google/Apple OAuth, passkeys, long-duration token expiry,
+existing-install session migration and the full upstream CI suite are outside
+this experiment's completed coverage. The package versions are pinned directly;
+transitive JavaScript dependencies are resolved at install time, following the
+existing standalone fixture convention. Record the generated lockfile when
+comparing a later dependency graph.
diff --git a/integration/templates/expo-kotlin-alignment/app.json b/integration/templates/expo-kotlin-alignment/app.json
new file mode 100644
index 00000000000..5a10563cd43
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/app.json
@@ -0,0 +1,24 @@
+{
+ "expo": {
+ "name": "Clerk Kotlin Alignment",
+ "slug": "clerk-kotlin-alignment",
+ "version": "1.0.0",
+ "android": {
+ "package": "com.clerk.expo.kotlin_alignment"
+ },
+ "plugins": [
+ "@clerk/expo",
+ [
+ "expo-build-properties",
+ {
+ "android": {
+ "enableMinifyInReleaseBuilds": true,
+ "enableShrinkResourcesInReleaseBuilds": true,
+ "buildArchs": ["arm64-v8a"],
+ "kotlinVersion": "2.4.20"
+ }
+ }
+ ]
+ ]
+ }
+}
diff --git a/integration/templates/expo-kotlin-alignment/index.js b/integration/templates/expo-kotlin-alignment/index.js
new file mode 100644
index 00000000000..5fd059fd067
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/index.js
@@ -0,0 +1,4 @@
+import { registerRootComponent } from 'expo';
+import App from './App';
+
+registerRootComponent(App);
diff --git a/integration/templates/expo-kotlin-alignment/package.json b/integration/templates/expo-kotlin-alignment/package.json
new file mode 100644
index 00000000000..b776a141c27
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "clerk-expo-kotlin-alignment-fixture",
+ "version": "1.0.0",
+ "private": true,
+ "main": "index.js",
+ "scripts": {
+ "android": "expo run:android --variant release",
+ "prebuild:android": "expo prebuild --clean --platform android"
+ },
+ "dependencies": {
+ "@clerk/expo": "4.7.1",
+ "expo": "58.0.0-preview.8",
+ "expo-auth-session": "58.0.5",
+ "expo-build-properties": "58.0.8",
+ "expo-constants": "58.0.8",
+ "expo-crypto": "58.0.2",
+ "expo-secure-store": "58.0.0",
+ "expo-status-bar": "58.0.1",
+ "expo-web-browser": "58.0.2",
+ "react": "19.3.0",
+ "react-dom": "19.3.0",
+ "react-native": "0.88.0-rc.2"
+ },
+ "packageManager": "pnpm@10.34.5"
+}
diff --git a/integration/templates/expo-kotlin-alignment/patches/clerk-expo.patch b/integration/templates/expo-kotlin-alignment/patches/clerk-expo.patch
new file mode 100644
index 00000000000..fad0125c252
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/patches/clerk-expo.patch
@@ -0,0 +1,94 @@
+diff --git a/android/build.gradle b/android/build.gradle
+--- a/android/build.gradle
++++ b/android/build.gradle
+@@ -59,10 +59,6 @@
+
+ kotlinOptions {
+ jvmTarget = "17"
+- // TODO: Remove once clerk-android pins its transitive deps to a Kotlin version compatible with Expo.
+- // clerk-android transitive deps (e.g. kotlinx-serialization-core:1.10.0) ship Kotlin 2.3.x metadata,
+- // but Expo SDK 54/55 uses Kotlin 2.1.x. This flag lets the 2.1.x compiler read 2.3.x metadata safely.
+- freeCompilerArgs += ['-Xskip-metadata-version-check']
+ }
+
+ buildFeatures {
+@@ -83,25 +79,9 @@
+ }
+ }
+
+-// clerk-android is compiled with Kotlin 2.3.x, but Expo/RN projects typically use an older
+-// Kotlin version (e.g. 2.1.x). This causes "incompatible version of Kotlin" metadata errors
+-// at :app:compileDebugKotlin. Applying -Xskip-metadata-version-check allows the older
+-// compiler to read 2.3.x metadata without failing.
+-try {
+- rootProject.allprojects {
+- tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+- compilerOptions {
+- freeCompilerArgs.add('-Xskip-metadata-version-check')
+- }
+- }
+- }
+-} catch (Exception ignored) {
+- // Future Gradle versions with Isolated Projects may block cross-project configuration.
+- // In that case, users should add '@clerk/expo' to their app.json plugins array instead,
+- // which applies the flag via the config plugin's withClerkAndroid.
+-}
+
+ dependencies {
++ implementation "androidx.savedstate:savedstate-compose:1.3.3"
+ testImplementation "junit:junit:4.13.2"
+
+ implementation project(':expo-modules-core')
+@@ -110,12 +90,8 @@
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
+
+ // Clerk Android SDK with prebuilt UI
+- // Exclude kotlin-stdlib to prevent 2.3.0 from polluting the project
+ // Exclude transitive OkHttp modules so the compatible versions below control resolution.
+ implementation("com.clerk:clerk-android-ui:$clerkAndroidUiVersion") {
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+ exclude group: 'com.squareup.okhttp3', module: 'okhttp'
+ exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection'
+ }
+@@ -123,9 +99,6 @@
+ // clerk-android-api. Pinning the api explicitly here keeps consumers
+ // compiling against the same version we ship the UI from.
+ implementation("com.clerk:clerk-android-api:$clerkAndroidApiVersion") {
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
+- exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+ exclude group: 'com.squareup.okhttp3', module: 'okhttp'
+ exclude group: 'com.squareup.okhttp3', module: 'okhttp-urlconnection'
+ }
+diff --git a/app.plugin.js b/app.plugin.js
+--- a/app.plugin.js
++++ b/app.plugin.js
+@@ -159,26 +159,6 @@
+ }
+ }
+ console.log('✅ Clerk Android packaging exclusions added');
+- }
+-
+- // --- Kotlin metadata version check skip ---
+- if (!buildGradle.includes('-Xskip-metadata-version-check')) {
+- const kotlinOptionsMatch = buildGradle.match(/kotlinOptions\s*\{/);
+- if (kotlinOptionsMatch) {
+- buildGradle = buildGradle.replace(
+- /kotlinOptions\s*\{/,
+- `kotlinOptions {\n // Clerk: allow reading metadata from newer Kotlin versions\n freeCompilerArgs += ['-Xskip-metadata-version-check']`,
+- );
+- } else {
+- const androidMatch = buildGradle.match(/android\s*\{/);
+- if (androidMatch) {
+- buildGradle = buildGradle.replace(
+- /android\s*\{/,
+- `android {\n kotlinOptions {\n // Clerk: allow reading metadata from newer Kotlin versions\n freeCompilerArgs += ['-Xskip-metadata-version-check']\n }`,
+- );
+- }
+- }
+- console.log('✅ Clerk Android Kotlin metadata version check skip added');
+ }
+
+ modConfig.modResults.contents = buildGradle;
diff --git a/integration/templates/expo-kotlin-alignment/pnpm-workspace.yaml b/integration/templates/expo-kotlin-alignment/pnpm-workspace.yaml
new file mode 100644
index 00000000000..4c8569d5db8
--- /dev/null
+++ b/integration/templates/expo-kotlin-alignment/pnpm-workspace.yaml
@@ -0,0 +1,5 @@
+packages:
+ - '.'
+
+patchedDependencies:
+ '@clerk/expo@4.7.1': patches/clerk-expo.patch