payloadcms / payloadcms/payload

Multipart parsing loses AsyncLocalStorage context in Cloudflare Workers

Open
#17,935 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
44.8k
Forks
4.2k
Avg merge
2d 21h
Merged PRs (30d)
53

Description

Describe the bug

Payload v4 multipart requests can lose the active AsyncLocalStorage context when they are parsed inside Cloudflare Workers.

processMultipart currently consumes the Fetch API request body by repeatedly awaiting request.body.getReader().read().
With a native workerd request body, those reader thenables do not retain the Node-compatible AsyncLocalStorage frame used by a request-scoped PostgreSQL adapter.
Payload then starts its database transaction without the active request store and the request returns HTTP 500.

This affects multipart authentication endpoints such as /api/users/first-register and /api/users/login, as well as multipart collection writes.
Equivalent JSON requests do not cross this parser and work normally.

The observed error was:

A PostgreSQL transaction requires an active Worker request.

Replacing the reader loop with await request.arrayBuffer() before feeding Busboy retained the request context and changed the exact live first-registration request from HTTP 500 to HTTP 200.
That workaround also made multipart login and Media collection creation succeed on the deployed Worker.

An unconditional arrayBuffer() is not proposed as the final upstream solution because it removes streaming and buffers the entire request before Busboy applies its limits.
The proposed fix retains streaming outside Cloudflare Workers and preserves existing upload-limit behavior.

Public reproduction

The isolated regression and proposed fix are in this public fork branch:

https://github.com/kolarskydominik/payload/tree/fix/cloudflare-multipart-async-context

The focused regression test is available directly at:

https://github.com/kolarskydominik/payload/blob/fix/cloudflare-multipart-async-context/packages/payload/src/uploads/fetchAPI-multipart/process-multipart.spec.ts

The first test models the failing runtime boundary by making direct stream-reader consumption fail while the Fetch Body promise remains available.
It fails against the current main implementation because getReader() is invoked.
The other tests verify that non-Cloudflare runtimes retain streaming and that Cloudflare multipart uploads retain the configured file-size limit.

Reproduction steps
  1. Configure Payload v4 with the TanStack Start adapter and deploy it to Cloudflare Workers with nodejs_compat.
  2. Use an AsyncLocalStorage store to scope the PostgreSQL/Hyperdrive client to the active Worker request.
  3. Open /admin/create-first-user with an empty Users collection.
  4. Submit the form, which sends multipart/form-data to /api/users/first-register.
  5. Observe that the database transaction starts after a request.body.getReader().read() await without the active request store and the endpoint returns HTTP 500.
  6. Consume the body with the Fetch Body promise before feeding Busboy and repeat the same request.
  7. Observe HTTP 200 and successful registration.
Affected areas
  • area: core
  • db: postgres
  • Cloudflare Workers runtime
Environment
Payload: 4.0.0-canary.29
@payloadcms/tanstack-start: 4.0.0-canary.29
Wrangler: 4.125.0
Cloudflare compatibility date: 2026-08-25
Cloudflare compatibility flags: nodejs_compat, global_fetch_strictly_public
PostgreSQL: 17 through Cloudflare Hyperdrive
pg: 8.20.0
Node.js: 26.6.0
pnpm: 10.34.5
Verified behavior

The workaround was verified against a deployed Worker rather than only a Node test.
The exact browser-generated multipart first-registration request returned HTTP 200, multipart logout/login returned HTTP 200, /api/users/me returned the authenticated user, and a disposable R2 multipart upload followed by Payload Media create/read/delete returned 200/200/200/201/200/200.

Contributor guide

Open the contributing guide

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.

Research direction

Start with packages/payload/src/uploads/fetchAPI-multipart/process-multipart.spec.ts and the processMultipart implementation. Run the focused regression tests to observe the reader behavior, then verify that the fix preserves streaming outside Cloudflare Workers, retains upload-size limits, and keeps multipart requests working in the affected runtime.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, postgresql, typescript
Domain
backend, cloud, databases, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.