Skip to content

ClerkRequest constructor consumes request body, breaking downstream body reads in non-native Request environments #8305

@gyulavoros

Description

@gyulavoros

Preliminary Checks

Reproduction

https://github.com/gyulavoros/clerk-body-issue

Publishable key

pk_test_bmF0aXZlLXBob2VuaXgtODcuY2xlcmsuYWNjb3VudHMuZGV2JA

Description

ClerkRequest's constructor in packages/backend/src/tokens/clerkRequest.ts transfers the request body when it only needs headers and cookies.

When called as new ClerkRequest(request) (the common path from authenticateRequest):

const url = typeof input !== 'string' && 'url' in input ? input.url : String(input);
super(url, init || typeof input === 'string' ? undefined : input);

This resolves to super(urlString, request), which passes the entire Request as init. The Request constructor reads init.body and transfers the ReadableStream, leaving the original request's body locked/consumed.

When this matters:

In runtimes where the Request body is a native buffered object (Bun.serve, Cloudflare Workers), the body survives the transfer. But when the body is a standard ReadableStream — as is the case in Node.js-based dev servers like Vite — the transfer consumes it. Any middleware or handler downstream that reads the body (e.g. tRPC, form handlers) gets "Body already used".

Reproduction:

Create a Hono app with clerkMiddleware() applied globally
Add a POST route that reads req.json() or req.formData()
Run through any Node.js-based server (Vite dev server, Express adapter, etc.) where the Request is constructed from an IncomingMessage with a ReadableStream body
POST to that route → "Body already used"

Suggested fix:

ClerkRequest only uses headers and cookies — it never reads the body. The constructor should avoid transferring it:

super(url, init || typeof input === 'string' ? undefined : { method: input.method, headers: input.headers });

Environment

System:
    OS: macOS 26.3.1
    CPU: (10) arm64 Apple M1 Max
    Memory: 331.73 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.7.0 - /Users/gyulavoros/.nvm/versions/node/v24.7.0/bin/node
    npm: 11.5.1 - /Users/gyulavoros/.nvm/versions/node/v24.7.0/bin/npm
    pnpm: 10.33.0 - /opt/homebrew/bin/pnpm
    bun: 1.3.12 - /Users/gyulavoros/.bun/bin/bun
  Browsers:
    Chrome: 147.0.7727.56
    Firefox: 133.0
    Safari: 26.3.1
  npmPackages:
    @clerk/hono: 0.1.11 => 0.1.11 
    @vikejs/hono: 0.2.0 => 0.2.0 
    @vitejs/plugin-react: 5.1.4 => 5.1.4 
    hono: 4.12.4 => 4.12.4 
    react: 19.2.4 => 19.2.4 
    react-dom: 19.2.4 => 19.2.4 
    vike: 0.4.257 => 0.4.257 
    vike-react: 0.6.20 => 0.6.20 
    vite: 8.0.8 => 8.0.8

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageA ticket that needs to be triaged by a team member

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions