cloudflare / cloudflare/workers-sdk
[vite-plugin] WebSocket upgrade forwards Accept-Encoding gzip; Worker throw becomes undici JSON.parse crash
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 186
Description
### What versions & operating system are you using?
- macOS (darwin 25.6.0), arm64
- Node v24.11.0
- @cloudflare/vite-plugin 1.43.0
- wrangler 4.107.0
- miniflare 4.20260701.0
- undici 7.28.0
### Please provide a link to a minimal reproduction
https://github.com/cloudflare/vinext/issues/2921
### Describe the Bug
`handleWebSocket` (`packages/vite-plugin-cloudflare/src/websockets.ts`) forwards a browser WebSocket upgrade to `miniflare.dispatchFetch` with `createHeaders(request)`, which copies **every** request header, including `Accept-Encoding: gzip`.
When the Worker throws on that upgrade, workerd returns a 500 JSON error with `Content-Encoding: gzip` and `MF-Experimental-Error-Stack`. miniflare's `dispatchFetch` then calls `response.json()` without decompressing (`DispatchFetchDispatcher` does not inflate). That rejects with:
```
SyntaxError: Unexpected token '\u001f', "\u001f�\b..." is not valid JSON
```
**On 1.43.0** the `upgrade` listener is `async` with no `try/catch`, so the rejection is unhandled and Node exits (exit 7). Vite HMR sockets (`sec-websocket-protocol` starting with `vite`) are skipped; Worker sockets are not.
**On current main**, #14862 wraps `dispatchFetch` in `try/catch` and `socket.destroy()`s. That stops the process death (good) but:
1. The Worker's exception is never revived or logged. The gzip `SyntaxError` is swallowed.
2. The client still gets no HTTP response. That is #15170 for the non-throwing non-101 case; a throwing Worker never even reaches the `!response.webSocket` branch, because `dispatchFetch` throws first.
Two complementary fixes, neither of which is "absorb gzip in the app":
1. Do not forward `Accept-Encoding` (and other content-coding headers) on the synthetic upgrade `dispatchFetch`. A 101 has no body; a 500 error page should not be gzipped into `response.json()`.
2. Keep the #14862 catch, but log / `reviveError` the rejection, and write a non-101 response when there is one (#15170).
The parse itself belongs in miniflare (sibling issue). vinext currently absorbs the gzip `SyntaxError` so `vinext dev` does not exit: https://github.com/cloudflare/vinext/pull/2922
### Please provide any relevant error logs
```
SyntaxError: Unexpected token '\u001f', "\u001f�\b..." is not valid JSON
at JSON.parse ()
at parseJSONFromBytes (undici)
at Miniflare.dispatchFetch (miniflare)
at Server. (@cloudflare/vite-plugin handleWebSocket upgrade)
```
Node then exits with code 7 on 1.43.0 because vinext's socket-error backstop rethrows anything that is not a peer-disconnect / benign asset import.
Contributor guide
Research direction
Start in packages/vite-plugin-cloudflare/src/websockets.ts at handleWebSocket and inspect the upgrade listener, then compare the current dispatchFetch catch with #14862. Run the linked minimal reproduction and verify that content-coding headers are not forwarded, Worker rejections are logged or revived, and an available non-101 response reaches the client.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- backend, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100