Skip to content

fix(react): drop non-simple params from fetchAccessToken (Hermes V1) - #368

Closed
ramonclaudio wants to merge 1 commit into
get-convex:mainfrom
ramonclaudio:fix/react-bridge-hermes-async-race
Closed

fix(react): drop non-simple params from fetchAccessToken (Hermes V1)#368
ramonclaudio wants to merge 1 commit into
get-convex:mainfrom
ramonclaudio:fix/react-bridge-hermes-async-race

Conversation

@ramonclaudio

@ramonclaudio ramonclaudio commented May 8, 2026

Copy link
Copy Markdown
Contributor

After sign-in on Expo SDK 56 (RN 0.85.3), useConvexAuth().isAuthenticated never flips to true. The session lands and /convex/token returns a JWT, but the bridge stays paused. Same on sign-up and sign-out.

Cause: Hermes V1 bug facebook/hermes#1761. An async function with non-simple params (destructuring, defaults, or rest) miscompiles when the build preserves native async, so await fn() resolves to undefined while the body runs later. fetchAccessToken hit this, and convex-js reads the undefined as "no token" and silently de-auths.

Who hits it (preserve-async builds only, default pipelines are safe):

  • Expo SDK 56 on babel-preset-expo below 56.0.6 (fixed upstream in 56.0.6 via expo/expo#45601, now the default).
  • Custom Metro configs, or bare RN 0.86+ with unstable_preserveAsync.

Fix: give fetchAccessToken simple params (plain options arg, forceRefreshToken read in the body, still async/await). Hermes then compiles it correctly on any pipeline.

The real fix is in Hermes (facebook/hermes#2030, #2045, #2046, all open). Revert this once a bundled Hermes ships them.

Repro: ramonclaudio/convex-better-auth-368-repro. Build, typecheck, and lint clean. Tests unchanged.

@vercel

vercel Bot commented May 8, 2026

Copy link
Copy Markdown

@ramonclaudio is attempting to deploy a commit to the Convex Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

fetchAccessToken was refactored to return an explicit new Promise wrapper. It still short-circuits to cachedToken when present (unless forceRefreshToken), reuses pendingTokenRef for concurrent callers, calls authClient.convex.token({ fetchOptions: { throw: false } }) for new requests, updates/clears cachedToken on success/failure, and always clears pendingTokenRef in finally; resolution/rejection from in-flight requests is routed through the wrapper’s resolve/reject.

Sequence Diagram(s)

sequenceDiagram
  participant Consumer
  participant fetchAccessToken
  participant Cache as cachedToken
  participant Pending as pendingTokenRef
  participant API as authClient.convex.token()

  Consumer->>fetchAccessToken: request token
  fetchAccessToken->>Cache: check cachedToken
  alt cachedToken present
    Cache-->>fetchAccessToken: return cached token
    fetchAccessToken-->>Consumer: resolve(token)
  else no cached token
    fetchAccessAccessToken->>Pending: check pendingTokenRef
    alt pending in flight
      Pending-->>fetchAccessToken: existing promise
      fetchAccessToken-->>Consumer: attach then(resolve,reject)
    else fetch new
      fetchAccessToken->>API: token({ throw: false })
      API-->>fetchAccessToken: token or error
      fetchAccessToken->>Cache: update or clear cachedToken
      fetchAccessToken-->>Consumer: resolve/reject via outer callbacks
      fetchAccessToken->>Pending: clear in finally
    end
  end
Loading

Possibly related PRs

Suggested reviewers

  • erquhart
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'drop non-simple params' but the actual change wraps the function in new Promise, not removing parameters. Update title to accurately reflect the change: 'fix(react): wrap fetchAccessToken in Promise to fix Hermes V1 async bug' or similar.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the issue (useConvexAuth not authenticating on Expo SDK 56), identifies the root cause (Hermes V1 async bug), details the fix (wrapping in Promise), and provides context about affected environments and upstream fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@dowski

dowski commented May 13, 2026

Copy link
Copy Markdown

Might this be fixed upstream in the Expo 56 preview?

expo/expo#45592

@ramonclaudio

Copy link
Copy Markdown
Contributor Author

Might this be fixed upstream in the Expo 56 preview?

expo/expo#45592

@dowski the expo team did patch it upstream, but only at the babel preset level (expo/expo#45601). That covers SDK 56 users on babel-preset-expo@56.0.6+, which ships by default in expo@next (preview.8+). Anyone on expo@canary still hits the bug because that tag froze at a pre-fix snapshot, but they can pin "babel-preset-expo": "~56.0.7" via npm overrides as a manual workaround. Same goes for bare React Native users who opt into unstable_preserveAsync.

Filed facebook/react-native#56816 to port the same babel transforms into @react-native/babel-preset so bare RN gets the same coverage, but that's on Meta's timeline and there is no guarantee on if/when it lands.

Leaving it up to @erquhart to decide what to do here!

@erquhart

Copy link
Copy Markdown
Member

Thanks Ray - holdilng this until expo 56 is closer to stable, would hope this would be addressed upstream.

Hermes V1 (250829098.x, RN 0.85 / Expo SDK 56) miscompiles async
functions with non-simple params: await resolves to undefined while the
body runs later, so useConvexAuth().isAuthenticated never flips true
after sign-in, sign-up, or sign-out. Take a plain identifier param and
read forceRefreshToken in the body so Hermes sees simple params.
Behavior is unchanged.

Drop once bundled Hermes ships the fix (facebook/hermes#2030, #2045,
#2046). Ref facebook/hermes#1761.
@ramonclaudio
ramonclaudio force-pushed the fix/react-bridge-hermes-async-race branch from 428390d to aa67695 Compare June 22, 2026 16:56
@ramonclaudio ramonclaudio changed the title fix(react): wrap fetchAccessToken in new Promise to fix useConvexAuth on Hermes V1 fix(react): drop non-simple params from fetchAccessToken (Hermes V1) Jun 22, 2026
@ramonclaudio

Copy link
Copy Markdown
Contributor Author

@erquhart going to close this out. Expo SDK 56 is stable and the babel-preset-expo fix (56.0.6+, now the default) covers Expo upstream, and there's been no traction here, so it's not worth carrying a source patch.

The real fix belongs in Hermes, which is what the RN team recommended on my port (react-native#56816). I opened hermes#2045 and #2046 to go with the existing #2030, and expect them to land eventually. Thanks!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants