Skip to content

wrangler dev exits with "Error inside ProxyWorker / Network connection lost" on POST requests with a body when assets is configured #15203

Description

@June157

What versions & operating system are you using?

  System:
    OS: Linux 6.8 Ubuntu 24.04.4 LTS 24.04.4 LTS (Noble Numbat)
    CPU: (6) x64 AMD EPYC-Milan Processor
    Memory: 13.01 GB / 15.61 GB
    Container: Yes
    Shell: 5.2.21 - /bin/bash
  Binaries:
    Node: 22.23.2 - /usr/bin/node
    npm: 10.9.8 - /usr/bin/npm
    pnpm: 9.0.0 - /usr/bin/pnpm
  npmPackages:
    wrangler: ^4.123.0 => 4.123.0

(workerd 1.20260811.1 and miniflare 5.20260811.1-alpha come in as wrangler's dependencies.)

Please provide a link to a minimal reproduction

https://gist.github.com/June157/7dd408450d8c153b47986a8c3325132c

(5 files: worker.mjs, wrangler.json, hammer.mjs, index.html → put under public/, and a README with the two commands.)

Describe the Bug

With Static Assets configured (assets.directory), wrangler dev exits — not just returns an error — after a few dozen POST requests that carry a body and target a path where an asset exists. The assets layer answers those requests with 405 itself (they never reach the user Worker), and after some number of them the ProxyWorker dies. The same POSTs without a body are fine indefinitely, and the same Worker without assets is fine indefinitely.

Client-side symptom: 405405 → one or two HTTP 500socket hang up / ECONNRESETECONNREFUSED for everything after. wrangler dev prints an empty ✘ [ERROR] and exits.

Production Workers with the same code do not exhibit this.

Minimal reproduction (also in the gist)

worker.mjs — its content barely matters; the crashing requests never reach it:

export default {
  fetch(request) {
    if (request.method === "POST") return new Response("Forbidden", { status: 403 });
    return new Response("ok");
  },
};

wrangler.json:

{ "name": "repro", "main": "worker.mjs", "compatibility_date": "2026-08-13",
  "assets": { "directory": "./public", "binding": "ASSETS" } }

public/index.html: any file (so that / is an asset path).

npx wrangler dev -c wrangler.json --port 8788
# in another shell — 300 POSTs with a small form body, 3 concurrent:
node --input-type=module -e '
const N=300,C=3,B="http://127.0.0.1:8788";let i=0,ok=0,fail=0;
const one=()=>fetch(B+"/",{method:"POST",body:"a=b",headers:{"content-type":"application/x-www-form-urlencoded"}});
await Promise.all(Array.from({length:C},async()=>{while(i<N){i++;try{const r=await one();if(r.status>=500)throw 0;ok++}catch{fail++}}}));
console.log({ok,fail});'

Observed: e.g. { ok: 107, fail: 193 }; afterwards curl http://127.0.0.1:8788/ → connection refused. Successful responses are 405 (from the assets layer, not from the Worker).

Expected: 300 × 405 (or the request forwarded to the Worker), and wrangler dev stays up.

What I varied (300 requests each, 2–3 runs each)

Variation Result
Same config, POST / without body ✅ 300 × 405, alive
Same config, POST /api/x (no asset there) with body → reaches Worker → 403 ✅ 300 × 403, alive
Same config, POST / with body, concurrency 1 ❌ dies (first failure at request 9)
No assets in config, POST / with body → 403 from Worker ✅ alive
+ compatibility_flags: ["nodejs_compat"] no change either way
+ assets.html_handling: "drop-trailing-slash" no change
+ public/_headers or public/_redirects no change
Worker drains the body (await request.text()) before responding irrelevant — request doesn't reach the Worker

So the necessary conditions I could isolate are: assets configured + POST with a body + path resolves to an asset (assets layer answers 405).

Where I hit it (possibly related, not reduced)

In an Astro site on @astrojs/cloudflare, the same crash signature also occurs on POST /api/contact (an SSR route, not an asset) when the request has a form body and no Origin header — Astro's CSRF middleware answers 403 without reading the body. The identical request with an Origin header (handler reads the body, answers 400) never crashes, and the same request without a body never crashes. I could not reduce that variant to a bare Worker: a bare Worker returning 403 to the same request on a non-asset path stays up. I mention it because it may be the same underlying stream-handling issue reached by a different route; happy to provide the Astro repro if useful.

Guess at the mechanism (unverified)

With assets, requests pass through an asset router before the user Worker. When the router itself answers (405 for POST-to-asset) while a request body is still in flight, the abandoned body stream seems to surface as Network connection lost inside the ProxyWorker and is treated as fatal for the whole dev server rather than for that one request.

Please provide any relevant error logs

Debug log (~/.config/.wrangler/logs/wrangler-*.log) at the moment of the crash:

Error in ProxyController: Error inside ProxyWorker
 Error
    at castErrorCause (…/wrangler/wrangler-dist/cli.js:179580:20)
    at ProxyController2.emitErrorEvent (…/wrangler/wrangler-dist/cli.js:280188:20)
    at ProxyController2.onProxyWorkerMessage (…/wrangler/wrangler-dist/cli.js:280065:18)
    at PROXY_CONTROLLER (…/wrangler/wrangler-dist/cli.js:279792:24)
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async #handleLoopbackCustomFetchService (…/miniflare/dist/src/index.js:111517:22)
    at async #handleLoopback (…/miniflare/dist/src/index.js:111748:20) {
  cause: {
    name: 'Error',
    message: 'Network connection lost.',
    stack: 'Error: Network connection lost.'
  }
}

The user Worker's runtime WebSocket had opened ~2 s earlier; there is nothing between Runtime.enable and this error in the debug log.

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:wranglerRelating to the `wrangler` package

    Type

    No type

    Projects

    Status
    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions