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
cloudflare/vinext#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:
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: cloudflare/vinext#2922
Please provide any relevant error logs
SyntaxError: Unexpected token '\u001f', "\u001f�\b..." is not valid JSON
at JSON.parse (<anonymous>)
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.<anonymous> (@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)
What versions & operating system are you using?
On current
main,Miniflare#dispatchFetchstill 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
cloudflare/vinext#2921
Describe the Bug
Miniflare#dispatchFetchtreats a workerd 500 that carriesMF-Experimental-Error-Stackas a structured exception and parses the body as JSON:(
packages/miniflare/src/index.ts,dispatchFetch. #14766 switched this toresponse.text()+JSON.parse, which has the same failure when the bytes are gzip.)DispatchFetchDispatcherdoes not decompress. It forwards the caller's headers, includingAccept-Encoding: gzip. workerd then returns the JSON error withContent-Encoding: gzip.response.json()/JSON.parse(text)therefore sees gzip magic1f 8b 08and throws:The
Content-Encodingstrip that follows this branch never runs, so the gzip is not rewritten toMF-Content-Encodingfirst.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:handleWebSocketcopies every upgrade header viacreateHeaders(including the browser'sAccept-Encoding: gzip) andawait 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 andsocket.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, thenreviveErrorthe Worker exception.Workaround on the vinext side: absorb this gzip
SyntaxErrorin the process-level socket-error backstop: cloudflare/vinext#2922Please provide any relevant error logs
Instrumented
Response.json()on the failing call:500content-type: application/jsoncontent-encoding: gzip1f8b0800000000000013(gzip magic)MF-Content-Encodingis unset (the strip after the ERROR_STACK branch did not run)