Dev server: plugins importing better-auth/api get a second copy of Better Auth's request state, so getOAuthState() is null in a plugin's callback hook

Open
#686 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
52/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript, vite

Research direction

Start by tracing how the void_worker environment configures Vite dependency optimization and how Void loads Better Auth at runtime; the report identifies defineRequestState in @better-auth/core as the source of the module-local ref. Reproduce with the described OAuth callback and compare against the better-auth exclusion in vite.config.ts. Done means the callback hook and Void runtime share request state in dev; verify the reported reproduction and relevant tests.

Written by the indexing model from the issue text.

Description

Void 1.5.0, better-auth 1.7.15, vite 8.3.5, @cloudflare/vite-plugin 2.0.0-beta.v.10, sqlite dialect, void dev.

A Better Auth plugin added in auth.ts registers an after hook on /callback/* and calls getOAuthState() from better-auth/api to read serverContext that the same plugin set with addOAuthServerContext() before calling signInSocial. On the dev server the hook gets null, although the state row in the verification table holds the serverContext and ctx.context.newSession is set. The same plugin works with betterAuth() used directly, outside Void.

Cause, as far as we can tell: Better Auth keys request state by a module-local ref (defineRequestState in @better-auth/core creates const ref = Object.freeze({}) per call; only the AsyncLocalStorage is on globalThis). The worker environment's optimizeDeps excludes void, so Void's runtime (which creates the auth instance and runs the callback) and user code importing better-auth/api (pre-bundled by Vite) load different instances of better-auth/dist/api/state/oauth.mjs. The callback's setOAuthState and the plugin's getOAuthState use different refs. Void already works around the same class of duplicate for its own auth ALS (authContext on a Symbol.for key).

Workaround: environments: { void_worker: { optimizeDeps: { exclude: ['better-auth'] } } } in vite.config.ts, after which the hook sees the state. A production build bundles one copy and is not affected.

Expected: user code and Void's runtime share one instance of better-auth on the dev server, for example by pre-bundling better-auth with Void's runtime, or by Void excluding better-auth along with itself.

Repro: a plugin with addOAuthServerContext({ x: 1 }) + signInSocial()(…) in a GET endpoint, and an after hook on /callback/ logging (await getOAuthState())?.serverContext. Run the flow against any OAuth provider (for example oauth2-mock-server through genericOAuth). It logs null under void dev and { x: 1 } with the exclusion above.

Dominant language
TypeScript
Stars
627
Forks
66
Avg merge
14h 38m
Merged PRs (30d)
179

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from voidzero-dev/void

All issues in voidzero-dev/void

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.