payloadcms / payloadcms/payload
Multipart parsing loses AsyncLocalStorage context in Cloudflare Workers
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:
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
- Configure Payload v4 with the TanStack Start adapter and deploy it to Cloudflare Workers with
nodejs_compat. - Use an
AsyncLocalStoragestore to scope the PostgreSQL/Hyperdrive client to the active Worker request. - Open
/admin/create-first-userwith an empty Users collection. - Submit the form, which sends
multipart/form-datato/api/users/first-register. - Observe that the database transaction starts after a
request.body.getReader().read()await without the active request store and the endpoint returns HTTP 500. - Consume the body with the Fetch Body promise before feeding Busboy and repeat the same request.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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