cloudflare / cloudflare/workers-sdk

[miniflare] dispatchFetch JSON-parses gzip-encoded ERROR_STACK 500 bodies

Open
#15,198 4 comments 0 reactions 0 assignees View on GitHub
package:miniflare
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
- miniflare 4.20260701.0 (via wrangler 4.107.0 / @cloudflare/vite-plugin 1.43.0)
- undici 7.28.0 (miniflare's copy)

On current `main`, `Miniflare#dispatchFetch` still JSON-parses the ERROR_STACK body (`JSON.parse(await response.text())` after #14766). That parse still fails if the body is gzip.

### Please provide a link to a minimal reproduction

https://github.com/cloudflare/vinext/issues/2921

### Describe the Bug

`Miniflare#dispatchFetch` treats a workerd 500 that carries `MF-Experimental-Error-Stack` as a structured exception and parses the body as JSON:

```js
const stack = response.headers.get(CoreHeaders.ERROR_STACK);
if (response.status === 500 && stack !== null) {
const caught = JsonErrorSchema.parse(await response.json());
throw reviveError(this.#workerSrcOpts, caught);
}
```

(`packages/miniflare/src/index.ts`, `dispatchFetch`. #14766 switched this to `response.text()` + `JSON.parse`, which has the same failure when the bytes are gzip.)

`DispatchFetchDispatcher` does not decompress. It forwards the caller's headers, including `Accept-Encoding: gzip`. workerd then returns the JSON error with `Content-Encoding: gzip`. `response.json()` / `JSON.parse(text)` therefore sees gzip magic `1f 8b 08` and throws:

```
SyntaxError: Unexpected token '\u001f', "\u001f�\b..." is not valid JSON
at JSON.parse
at parseJSONFromBytes (undici)
```

The `Content-Encoding` strip that follows this branch never runs, so the gzip is not rewritten to `MF-Content-Encoding` first.

This is the same `response.json()` fragility as #8917 (empty HEAD body). Here the body is present but compressed.

**How it shows up with `@cloudflare/vite-plugin`:** `handleWebSocket` copies every upgrade header via `createHeaders` (including the browser's `Accept-Encoding: gzip`) and `await miniflare.dispatchFetch(...)`. When the Worker throws on a WebSocket upgrade, this gzip parse rejects. On vite-plugin 1.43.0 that rejection is unhandled and kills the Node process. On current main, #14862 catches it and `socket.destroy()`s, so the process lives but the Worker's exception is never revived or logged. Related: #15170 (non-101 upgrade responses are destroyed).

**Expected:** decompress (or ignore `Accept-Encoding`) before parsing the ERROR_STACK body, then `reviveError` the Worker exception.

**Workaround on the vinext side:** absorb this gzip `SyntaxError` in the process-level socket-error backstop: 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/lib/web/infra/index.js)
at successSteps (undici/lib/web/fetch/body.js)
at Miniflare.dispatchFetch (miniflare/dist/src/index.js)
at Server. (@cloudflare/vite-plugin/dist/index.mjs) // handleWebSocket upgrade
```

Instrumented `Response.json()` on the failing call:

- status `500`
- `content-type: application/json`
- `content-encoding: gzip`
- body starts `1f8b0800000000000013` (gzip magic)
- `MF-Content-Encoding` is unset (the strip after the ERROR_STACK branch did not run)

Contributor guide

Open the contributing guide

Research direction

Start in packages/miniflare/src/index.ts at Miniflare#dispatchFetch and inspect the ERROR_STACK handling alongside the Content-Encoding logic. Run the linked minimal reproduction or an equivalent gzip-encoded 500 response. Done means the compressed ERROR_STACK body is handled without a JSON parse failure and the Worker exception is revived.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.