cloudflare / cloudflare/workers-sdk

[vite-plugin] WebSocket upgrade forwards Accept-Encoding gzip; Worker throw becomes undici JSON.parse crash

Open
#15,199 4 comments 0 reactions 0 assignees View on GitHub
package:vite-plugin
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.