cloudflare / cloudflare/workers-sdk
miniflare: uncaught AssertionError `typeHeader === "Promise"` in #parseAsyncResponse kills vite dev when calling a cross-process Durable Object over the dev registry
- Dominant language
- TypeScript
- Stars
- 4.5k
- Forks
- 1.5k
- Avg merge
- 3d 8h
- Merged PRs (30d)
- 186
Description
### Which Cloudflare product(s) does this pertain to?
Miniflare, Wrangler, Cloudflare Vite Plugin
### What versions are you using?
- `wrangler`: 4.115.0 (also reproduced on 4.107.0)
- `@cloudflare/vite-plugin`: 1.48.0 (also reproduced on 1.43.0)
- `miniflare`: 4.20260722.1 (bundled; also reproduced on 4.20260701.0)
- Node.js: v22.22.2
- OS: macOS (darwin 25.0.0, Apple Silicon)
### Describe the bug
When two Vite dev servers are linked through the dev registry — a SvelteKit app worker binding a Durable Object exported by a second worker via `script_name` — the app's dev process is killed by an unhandled assertion inside miniflare while parsing async RPC responses coming back through the cross-process proxy:
```
node:internal/process/promises:394
triggerUncaughtException(err, true /* fromPromise */);
^
AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:
(typeHeader === "Promise")
at #parseAsyncResponse (…/node_modules/miniflare/dist/src/index.js:92607:38)
at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
```
Because the rejection is uncaught, the whole `vite dev` process exits — it does not degrade to a 500 for the offending request.
Frequency went from intermittent (every few dozen DO RPC calls) to near-systematic on some routes. The two call paths we see crash:
1. Plain RPC method calls on the DO stub (`stub.someMethod(args)` returning a JSON-serializable array) issued during SSR.
2. The teardown path when a WebSocket bound to the DO (via partyserver) disconnects.
### Setup
- Worker A (SvelteKit app, `@cloudflare/vite-plugin` + `vite dev`, port 5173) — binds the DO:
```jsonc
"durable_objects": {
"bindings": [
{ "name": "ORGANIZATIONS", "class_name": "Organization", "script_name": "worker" }
]
}
```
- Worker B (`name: "worker"`, second `vite dev` process, port 5174) — exports the `Organization` DO (SQLite-backed, `new_sqlite_classes`).
- Both processes discover each other through the dev registry.
- Worker B also has `remote: true` bindings (R2, browser rendering), in case that interacts with the proxy code path.
### Reproduction steps
1. Start worker B (`vite dev`, exports a DO class with an async RPC method returning an array of plain objects).
2. Start worker A (`vite dev`, SvelteKit) with the DO bound via `script_name`.
3. From worker A, call the DO method repeatedly during SSR (e.g. on each page load).
4. Within a few navigations, worker A's process dies with the assertion above.
### Expected behavior
Cross-process DO RPC through the dev registry either succeeds or fails the individual request; a malformed/unexpected async response frame should not crash the entire dev server via an unhandled rejection.
### Notes
- Running both workers in a single dev process via `auxiliaryWorkers` avoids this specific assertion (no cross-process proxy), but is not a workaround for us because WebSocket connections to the DO do not work in that mode.
- Happy to provide a minimal repro repository if useful.
Contributor guide
Research direction
Reproduce the cross-process Durable Object call with two Vite dev servers and inspect Miniflare’s `dist/src/index.js` at `#parseAsyncResponse`, where the `typeHeader === "Promise"` assertion occurs. Trace the dev-registry proxy response frames for RPC and WebSocket teardown paths. Done means the dev server stays running and each request succeeds or reports an individual failure instead of an unhandled process-killing rejection.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript, vite
- Domain
- devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100