cloudflare / cloudflare/workers-sdk
`wrangler dev` exits with "Error inside ProxyWorker / Network connection lost" on POST requests with a body when `assets` is configured
- 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?
```
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: `405` … `405` → one or two `HTTP 500` → `socket hang up` / `ECONNRESET` → `ECONNREFUSED` 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:
```js
export default {
fetch(request) {
if (request.method === "POST") return new Response("Forbidden", { status: 403 });
return new Response("ok");
},
};
```
`wrangler.json`:
```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).
```bash
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=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.
Contributor guide
Research direction
Run the reproduction from README using wrangler.json, worker.mjs, public/index.html, and the POST workload described in hammer.mjs. Start by tracing the wrangler dev ProxyWorker and assets request path around the reported Network connection lost error; done means repeated body-bearing POST requests to an asset return 405 or reach the Worker without terminating the dev server.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- cli, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100