Skip to content

TS2883: inferred return types from withAuth() / refreshSession() are non-portable when consumer pins a different @workos-inc/node version #422

@Leestex

Description

@Leestex

Describe the bug

TypeScript emits TS2883 (and on some configurations, the related TS2742) on consumer code that infers return types from withAuth() or refreshSession():

error TS2883: The inferred type of 'requireAuth' cannot be named without a reference to 'User' from
'@workos-inc/authkit-nextjs/node_modules/@workos-inc/node'. This is likely not portable.
A type annotation is necessary.

The error reproduces whenever the consumer's resolved version of @workos-inc/node differs from the version that satisfies authkit-nextjs's transitive ^9.0.0 range. Package managers then install a duplicate nested copy under node_modules/@workos-inc/authkit-nextjs/node_modules/@workos-inc/node/, and TypeScript can no longer write a portable declaration for any inferred type that surfaces User (or OauthTokens, AuthenticationResponse) through the public API.

To Reproduce

  1. In a Next.js project, install @workos-inc/authkit-nextjs@^4.0.1 and pin @workos-inc/node directly to a version that doesn't match ^9.0.0's currently-resolved point version, e.g. 9.2.0 while authkit-nextjs's transitive resolution is still at 9.1.1. With Yarn 4 + node-modules linker this is what dependabot[bot] produces every time it bumps the direct pin.

  2. Write any function whose return type is inferred from withAuth(). A minimal repro:

    // lib/auth/session.ts
    import { withAuth, getSignInUrl } from "@workos-inc/authkit-nextjs"
    import { redirect } from "next/navigation"
    
    export async function requireAuth() {
      const session = await withAuth()
      if (!session.user) {
        const signInUrl = await getSignInUrl()
        redirect(signInUrl)
      }
      return session as typeof session & { user: NonNullable<typeof session.user> }
    }
  3. Run tsc --noEmit (or in any monorepo package with declaration: true / composite: true / isolatedDeclarations).

  4. Observe TS2883 on the requireAuth declaration.

Confirmed in yarn.lock that there are two resolved versions of @workos-inc/node:

"@workos-inc/node@npm:9.2.0":         # consumer direct dep
"@workos-inc/node@npm:^9.0.0":        # transitive from authkit-nextjs → 9.1.1

yarn dedupe --check '@workos-inc/node' reports the split:

@workos-inc/node@npm:^9.0.0 can be deduped from @workos-inc/node@npm:9.1.1 to @workos-inc/node@npm:9.2.0

Expected behavior

tsc should emit a portable declaration. authkit-nextjs's public types should resolve to a single canonical copy of @workos-inc/node's types regardless of the consumer's resolved version, so that any inferred type referencing User / UserInfo / Session / HandleAuthSuccessData is namable from a stable path.

Root cause

@workos-inc/node is declared in dependencies (https://github.com/workos/authkit-nextjs/blob/main/package.json#L41), but its types are surfaced into authkit-nextjs's public-API interfaces in src/interfaces.ts:

import type { AuthenticationResponse, OauthTokens, User } from '@workos-inc/node';

export interface Session  { user: User; ... }
export interface UserInfo { user: User; ... }
export interface HandleAuthSuccessData extends Session {
  oauthTokens?: OauthTokens;
  authenticationMethod?: AuthenticationResponse['authenticationMethod'];
  ...
}

A regular dependencies entry on a SDK whose types appear in the public API allows the package manager to install a separate nested copy whenever the consumer's resolved version diverges. The same hazard exists with next and react — and authkit-nextjs already declares both as peerDependencies for exactly this reason.

Proposed fix

Move @workos-inc/node from dependencies to peerDependencies. The peer-dep contract forces a single resolved copy in the consumer tree by construction. PR coming.

This is a breaking change, but in practice the impact is small: every authkit-nextjs consumer already has to call @workos-inc/node directly for the parts of the WorkOS SDK that authkit doesn't wrap (organization management, user management, FGA, audit logs, etc.).

Workaround (for anyone hitting this today)

yarn dedupe '@workos-inc/node' in the consumer's repo collapses the duplicate, and either a resolutions pin or a CI-side yarn dedupe --check '@workos-inc/node' step (run after install) prevents the regression from recurring on future Dependabot bumps.

Environment

  • authkit-nextjs version: 4.0.1 (also affects all 4.x; same class of bug applied to earlier majors with their own @workos-inc/node ranges)
  • @workos-inc/node versions involved: 9.1.1 (transitive) vs 9.2.0 (consumer direct)
  • Next.js version: any
  • TypeScript version: 6.0.x (also reproduces on 5.x with the same diagnostic code)
  • Package manager: Yarn 4.14.1 with nodeLinker: node-modules (the same hazard exists on npm/pnpm whenever versions split)
  • OS: macOS / Linux (build-time only — not runtime, not browser-dependent)

Additional context

Same root cause manifested as TS2742 on earlier versions before TS6 changed the diagnostic. Closed issue #181 (1.x era) was a related-but-different surfacing of the public-types-export problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions