Skip to content

Conversation

@Ephem
Copy link
Contributor

@Ephem Ephem commented Dec 15, 2025

Description

While debugging why the TanStack Start tests were failing in my branch that refactors to useSyncExternalStore I found that the useAwaitableNavigate was resolving before the navigation finished. This combined with timing changes in my branch that should have been inconsequential, were breaking the tests.

I am still not a 100% sure of the reason. This PR essentially turns this:

res(navigate(options));

to this:

navigate(options).then(res);

which fixes the tests, but since await unwraps nested promises, the original code should have been working. This might be a timing thing where the extra .then makes things resolve slightly later. 🤔 I will try to get to the bottom of this.

I also removed the startTransition that wraps the whole thing, because I did not see the point of it. Using a transition or not for the navigation should be a framework level concern, not a Clerk one. I'd be happy for pushback on this decision, but from my (albeit somewhat limited) testing, things seem to behave the same way before and after.

The reason removing startTransition might be important (now or in the future) is that if the framework wants to do something synchronously before the transition starts, it currently can't since we are wrapping it.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • Bug Fixes
    • Fixed a navigation edge case where authentication state updates could be applied before navigation completed.

✏️ Tip: You can customize this high-level summary in your review settings.

@Ephem Ephem requested review from octoper and wobsoriano December 15, 2025 13:42
@changeset-bot
Copy link

changeset-bot bot commented Dec 15, 2025

🦋 Changeset detected

Latest commit: 8b3574c

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

This PR includes changesets to release 1 package
Name Type
@clerk/tanstack-react-start Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Dec 15, 2025

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

Project Deployment Review Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Dec 15, 2025 1:42pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 15, 2025

Walkthrough

A patch release fixes a navigation edge case in @clerk/tanstack-react-start by removing React's useTransition hook from the awaitable navigate function. The Promise now resolves after navigation completes, ensuring auth state updates are applied in the correct sequence.

Changes

Cohort / File(s) Summary
Release documentation
.changeset/mighty-hands-crash.md
New changeset entry documenting the patch release and navigation edge case fix.
Navigation promise resolution fix
packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
Removed useTransition hook and associated import; simplified Resolve type from (value?: unknown) => void to () => void; Promise returned by awaitable navigate now resolves as Promise<void> immediately after navigation completes rather than within a transition.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Changes are focused and localized to a single implementation file plus documentation
  • Logic modifications are straightforward: transition removal and promise resolution timing adjustment
  • Key consideration: verify that removing useTransition doesn't introduce timing issues elsewhere and that the new resolution behavior is compatible with calling code

Poem

🐰 A hop through the promise land so neat,
Where auth and nav now kindly meet,
No transition tricks, just straight and true—
The edge case vanquished, bugs bid adieu!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: resolving a navigation promise timing issue in useAwaitableNavigate.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fredrik/fix-tanstack-useAwaitableNavigate

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 15, 2025

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@7463

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@7463

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 8b3574c

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 223143f and 8b3574c.

📒 Files selected for processing (2)
  • .changeset/mighty-hands-crash.md (1 hunks)
  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

All code must pass ESLint checks with the project's configuration

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.{js,jsx,ts,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use Prettier for consistent code formatting

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
packages/**/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

TypeScript is required for all packages

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Follow established naming conventions (PascalCase for components, camelCase for variables)

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
packages/**/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

packages/**/src/**/*.{ts,tsx,js,jsx}: Maintain comprehensive JSDoc comments for public APIs
Use tree-shaking friendly exports
Validate all inputs and sanitize outputs
All public APIs must be documented with JSDoc
Use dynamic imports for optional features
Provide meaningful error messages to developers
Include error recovery suggestions where applicable
Log errors appropriately for debugging
Lazy load components and features when possible
Implement proper caching strategies
Use efficient data structures and algorithms
Implement proper logging with different levels

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.ts?(x)

📄 CodeRabbit inference engine (.cursor/rules/development.mdc)

Use proper TypeScript error types

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/typescript.mdc)

**/*.{ts,tsx}: Always define explicit return types for functions, especially public APIs
Use proper type annotations for variables and parameters where inference isn't clear
Avoid any type - prefer unknown when type is uncertain, then narrow with type guards
Implement type guards for unknown types using the pattern function isType(value: unknown): value is Type
Use interface for object shapes that might be extended
Use type for unions, primitives, and computed types
Prefer readonly properties for immutable data structures
Use private for internal implementation details in classes
Use protected for inheritance hierarchies
Use public explicitly for clarity in public APIs
Use mixins for shared behavior across unrelated classes in TypeScript
Use generic constraints with bounded type parameters like <T extends { id: string }>
Use utility types like Omit, Partial, and Pick for data transformation instead of manual type construction
Use discriminated unions instead of boolean flags for state management and API responses
Use mapped types for transforming object types
Use conditional types for type-level logic
Leverage template literal types for string manipulation at the type level
Use ES6 imports/exports consistently
Use default exports sparingly, prefer named exports
Document functions with JSDoc comments including @param, @returns, @throws, and @example tags
Create custom error classes that extend Error for specific error types
Use the Result pattern for error handling instead of throwing exceptions
Use optional chaining (?.) and nullish coalescing (??) operators for safe property access
Let TypeScript infer obvious types to reduce verbosity
Use const assertions with as const for literal types
Use satisfies operator for type checking without widening types
Declare readonly arrays and objects for immutable data structures
Use spread operator and array spread for immutable updates instead of mutations
Use lazy loading for large types...

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Use ESLint with custom configurations tailored for different package types

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
**/*.{js,ts,jsx,tsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (.cursor/rules/monorepo.mdc)

Use Prettier for code formatting across all packages

Files:

  • packages/tanstack-react-start/src/client/useAwaitableNavigate.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (25)
  • GitHub Check: Integration Tests (handshake:staging, chrome)
  • GitHub Check: Integration Tests (quickstart, chrome, 15)
  • GitHub Check: Integration Tests (billing, chrome, RQ)
  • GitHub Check: Integration Tests (nextjs, chrome, 16, RQ)
  • GitHub Check: Integration Tests (billing, chrome)
  • GitHub Check: Integration Tests (nextjs, chrome, 16)
  • GitHub Check: Integration Tests (quickstart, chrome, 16)
  • GitHub Check: Integration Tests (nextjs, chrome, 15)
  • GitHub Check: Integration Tests (machine, chrome)
  • GitHub Check: Integration Tests (machine, chrome, RQ)
  • GitHub Check: Integration Tests (custom, chrome)
  • GitHub Check: Integration Tests (generic, chrome)
  • GitHub Check: Integration Tests (handshake, chrome)
  • GitHub Check: Integration Tests (react-router, chrome)
  • GitHub Check: Integration Tests (express, chrome)
  • GitHub Check: Integration Tests (vue, chrome)
  • GitHub Check: Integration Tests (tanstack-react-start, chrome)
  • GitHub Check: Integration Tests (nuxt, chrome)
  • GitHub Check: Integration Tests (astro, chrome)
  • GitHub Check: Integration Tests (sessions:staging, chrome)
  • GitHub Check: Integration Tests (sessions, chrome)
  • GitHub Check: Integration Tests (ap-flows, chrome)
  • GitHub Check: Integration Tests (localhost, chrome)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: semgrep-cloud-platform/scan
🔇 Additional comments (1)
.changeset/mighty-hands-crash.md (1)

1-5: LGTM! Changeset is well-formed.

The changeset format is correct, the patch version bump is appropriate for a bug fix, and the description clearly communicates the fix to users.

resolveAll();
}, [location]);

return (options: NavigateOptions) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type annotation.

Per coding guidelines, functions should have explicit return types, especially for public APIs. The returned function should explicitly declare its return type.

As per coding guidelines: "Always define explicit return types for functions, especially public APIs."

- return (options: NavigateOptions) => {
+ return (options: NavigateOptions): Promise<void> => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (options: NavigateOptions) => {
return (options: NavigateOptions): Promise<void> => {
🤖 Prompt for AI Agents
In packages/tanstack-react-start/src/client/useAwaitableNavigate.ts around line
20, the returned function currently lacks an explicit return type; update the
signature to explicitly annotate the return type (e.g., change "return (options:
NavigateOptions) => {" to "return (options: NavigateOptions) => Promise<void> {"
or the correct concrete return type used by the implementation) so the public
API has a clear, explicit return-type annotation.

Comment on lines 20 to 30
return (options: NavigateOptions) => {
return new Promise(res => {
startTransition(() => {
resolveFunctionsRef.current.push(res);
res(navigate(options));
});
return new Promise<void>(res => {
resolveFunctionsRef.current.push(res);
navigate(options)
.then(() => {
res();
})
.catch(() => {
res();
});
});
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 15, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the full file to understand the context
cat -n packages/tanstack-react-start/src/client/useAwaitableNavigate.ts

Repository: clerk/javascript

Length of output: 1146


🏁 Script executed:

#!/bin/bash
# Search for tests related to useAwaitableNavigate
find . -type f \( -name "*.test.*" -o -name "*.spec.*" \) -exec rg -l "useAwaitableNavigate" {} \;

Repository: clerk/javascript

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Search for usage of useAwaitableNavigate in the codebase
rg -n "useAwaitableNavigate" --type=ts --type=tsx -B2 -A2 | head -100

Repository: clerk/javascript

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Search for usage patterns with broader search
rg "useAwaitableNavigate" -A 5 -B 5 --type=ts --type=js --type=jsx

Repository: clerk/javascript

Length of output: 87


🏁 Script executed:

#!/bin/bash
# Look for any test files in the tanstack-react-start package
find packages/tanstack-react-start -type f \( -name "*.test.*" -o -name "*.spec.*" \) 2>/dev/null | head -20

Repository: clerk/javascript

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Check if there are any comments or git history that explains the design
rg -B 10 "useAwaitableNavigate" packages/tanstack-react-start/src/client/useAwaitableNavigate.ts | head -30

Repository: clerk/javascript

Length of output: 289


🏁 Script executed:

#!/bin/bash
# Check the exports test file
cat packages/tanstack-react-start/src/__tests__/exports.test.ts

Repository: clerk/javascript

Length of output: 1115


🏁 Script executed:

#!/bin/bash
# Search for actual usage in source files (avoiding test files)
rg "useAwaitableNavigate" packages/tanstack-react-start/src --type-add='tsx:*.tsx' --type=ts

Repository: clerk/javascript

Length of output: 579


🏁 Script executed:

#!/bin/bash
# Check package index files for exports
cat packages/tanstack-react-start/src/client/index.ts

Repository: clerk/javascript

Length of output: 193


🏁 Script executed:

#!/bin/bash
# Read ClerkProvider to understand how useAwaitableNavigate is used
cat -n packages/tanstack-react-start/src/client/ClerkProvider.tsx

Repository: clerk/javascript

Length of output: 2843


🏁 Script executed:

#!/bin/bash
# Verify if navigate() actually triggers location change or if they're separate
rg -A 20 "navigate\(" packages/tanstack-react-start/src/client/useAwaitableNavigate.ts | head -30

Repository: clerk/javascript

Length of output: 201


🏁 Script executed:

#!/bin/bash
# Check if there are any issues or PRs mentioning this hook
git log --oneline --all packages/tanstack-react-start/src/client/useAwaitableNavigate.ts 2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 227


🏁 Script executed:

#!/bin/bash
# Check the details of the commit that supposedly fixed this issue
git show 8b3574c5 --stat packages/tanstack-react-start/src/client/useAwaitableNavigate.ts

Repository: clerk/javascript

Length of output: 380


🏁 Script executed:

#!/bin/bash
# Get full diff of that commit for the file
git show 8b3574c5 packages/tanstack-react-start/src/client/useAwaitableNavigate.ts

Repository: clerk/javascript

Length of output: 1454


Critical: Double resolution and memory leak in navigation promise logic.

The promise resolution logic has a critical flaw:

  1. Memory leak: When navigate() completes, res() is called immediately (lines 25, 28), but the resolve function remains in the resolveFunctionsRef.current array. These functions are only removed when location changes (via resolveAll() at line 13). Rapid navigation calls will accumulate resolved functions in the array indefinitely until a location change occurs.

  2. Double resolution: The same res() function is called both when navigate() completes (lines 25, 28) and when location changes (line 12 via resolveAll()). While JavaScript ignores subsequent resolve() calls on an already-resolved promise, this indicates confused logic about when resolution should occur.

  3. Unclear intent: The implementation resolves on both navigate() completion and location change, which seems contradictory. Clarify the intended behavior:

    • Option A: If resolution should happen when location changes, remove the .then() and .catch() handlers (lines 24-29) and rely solely on the useEffect.
    • Option B: If resolution should happen when navigate() completes, clean up the resolve function immediately after calling res():
    return new Promise<void>(res => {
+     const index = resolveFunctionsRef.current.push(res) - 1;
-     resolveFunctionsRef.current.push(res);
      navigate(options)
        .then(() => {
          res();
+         resolveFunctionsRef.current.splice(index, 1);
        })
        .catch(() => {
          res();
+         resolveFunctionsRef.current.splice(index, 1);
        });
    });

Note: Option B assumes sequential resolution, which may not hold with concurrent navigations.

🤖 Prompt for AI Agents
In packages/tanstack-react-start/src/client/useAwaitableNavigate.ts around lines
20-30, the current implementation pushes resolve functions into
resolveFunctionsRef.current then also calls res() in the navigate().then/.catch
handlers, causing both a memory leak (resolved functions remain in the array
until location changes) and double-resolution; fix by choosing one resolution
strategy—recommended Option A: remove the .then() and .catch() handlers entirely
so the promise resolves only via the existing resolveAll() in the
location-change effect (ensure resolveAll clears the array after invoking each
resolver); alternatively, if you prefer resolution when navigate() completes
(Option B), then after calling res() immediately remove that resolve function
from resolveFunctionsRef.current (and guard so each resolver is invoked only
once) to avoid accumulation and duplicate calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wobsoriano @octoper I think this comment makes a lot of sense and articulates something I've been struggling with as well. Feels like we should either rely on the useEffect approach, or awaiting the navigate one, but using both feels like it makes things hard to reason about. 🤔

Because of #7411 I'm leaning towards the useEffect solution that we are using in other packages too. This should guarantee the page has fully mounted before we emit the new navigation state, which seems safer. When navigate resolves is out of our control, determined by the framework, and could even change subtly in the future.

What do you think?

I'll verify if removing the resolving on navigate still fixes the tests in my branch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Turns out that wont work well either, because in TanStack Start, location does not seem to change if navigating to the same url you are already on, so promise never resolves in that case.. 🤔

Copy link
Member

Choose a reason for hiding this comment

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

ff.

thanks for checking this one out, frederik!

@Ephem
Copy link
Contributor Author

Ephem commented Dec 15, 2025

I think resolveAll actually runs too early and that this PR might now mess up the transitive state, so we no longer wait until the next page has properly loaded before applying the new auth state. Will keep working on it.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants