This is the same symptom as #4562 ("Network connection lost." surfaced as "Error inside ProxyWorker"), which was closed in the 3.x line. We see it on 4.125.0 through a different code path, so I am filing it as a regression rather than a new report.
What versions & operating system are you using?
The failure happens only in CI, so the workstation envinfo would describe a machine where the bug does not occur. The environment that matters:
- GitHub Actions
ubuntu-latest, private repository — 2 cores, 8 GB
- Node 24
wrangler 4.125.0
@cloudflare/workerd-linux-64 1.20260820.1
miniflare 5.20260820.0-alpha
- Next.js 16.2.12 via
@opennextjs/cloudflare
- Two
wrangler dev servers running concurrently on the same runner
Please provide a link to a minimal reproduction
I do not have one, and I would rather say so than link something that does not reproduce.
The trigger is environmental — a connection dropped mid-response, which we only ever see under contention on a small runner. But the defect itself is in wrangler's own source and does not depend on our code: the path below produces an empty Error unconditionally, for any project. The analysis is source-level, and I have included the exact code so it can be checked without running anything.
Happy to provide more logs or to test a patch.
Describe the Bug
wrangler dev exits mid-session with:
✘ [ERROR]
🪵 Logs were written to ~/.config/.wrangler/logs/wrangler-<date>.log
ERROR Wrangler dev command failed:
The [ERROR] line carries no message at all. The real one is in the log file, at debug level:
Error in ProxyController: Error inside ProxyWorker
at castErrorCause (wrangler-dist/cli.js)
at ProxyController.emitErrorEvent
at ProxyController.onProxyWorkerMessage
cause: {
name: 'Error',
message: 'Network connection lost.',
stack: 'Error: Network connection lost.'
}
So the information exists. It is simply not on the line a user reads.
Why the printed error is empty. ProxyWorker.js posts the failure to the controller as a plain object, because an Error cannot cross the worker boundary:
void fetch(userWorkerUrl, new Request(request, { headers }))
.then(...)
.catch((error) => {
if (isSameUserWorkerOrigin(userWorkerUrl, this.proxyData?.userWorkerUrl)) {
void sendMessageToProxyController(this.env, {
type: "error",
error: { name: error.name, message: error.message,
stack: error.stack, cause: error.cause },
});
cli.js then reconstructs it:
function castErrorCause(cause) {
if (cause instanceof Error) return cause;
const error = new Error(); // no message
error.cause = cause;
return error;
}
The posted value is never an Error, so this path always yields an Error with an empty message. emitErrorEvent dispatches it, and the dev server stops.
wrangler already treats two neighbouring conditions as non-fatal. DevEnv.handleErrorEvent has an exemption branch, and it is two reasons wide:
handleErrorEvent(event) {
if (event.cause instanceof MiniflareCoreError && event.cause.isUserError())
this.emit("error", new UserError(...));
else if (event.source === "ProxyController" &&
(event.reason.startsWith("Failed to send message to") ||
event.reason.startsWith("Could not connect to InspectorProxyWorker"))) {
logger.debug(`Error in ${event.source}: ${event.reason}`, event.cause);
logger.debug("=> Error contextual data:", event.data);
} else this.emit("error", event); // fatal
}
The design already recognises that a ProxyController error can be transient and should only be logged. Error inside ProxyWorker — a rejected proxy fetch — is the same kind of condition, and falls through to the fatal else. There is no configuration escape: the fatality is hardcoded.
Why it matters beyond a missing message. The rejection is transient: one connection cut mid-response is enough. A single one takes the whole dev server down, and every request after it fails. In our end to end suite that is 169 tests failing for one dropped connection, with nothing on the console to say why.
Two suggestions, in order of value:
castErrorCause could rebuild a real Error from the posted shape, which already carries name, message and stack. The information exists and is discarded at the exact moment a user needs it.
Error inside ProxyWorker could join the exemption branch above. It is a transient network condition, not a configuration fault, and the code already makes that distinction for its two neighbours.
Please provide any relevant error logs
Observed 8 times over 31 CI runs. The crash always lands on the same test, and the number of failures after it is identical every time. Never reproduced on a developer machine, where the same suite passes.
One data point that may help you place it: after splitting our suite so each job runs one server instead of two, the crash has not recurred — 2 runs, which we do not consider proof. That is consistent with contention producing the dropped connection. The dropped connection being fatal is the part being reported here.
This is the same symptom as #4562 ("Network connection lost." surfaced as "Error inside ProxyWorker"), which was closed in the 3.x line. We see it on 4.125.0 through a different code path, so I am filing it as a regression rather than a new report.
What versions & operating system are you using?
The failure happens only in CI, so the workstation
envinfowould describe a machine where the bug does not occur. The environment that matters:ubuntu-latest, private repository — 2 cores, 8 GBwrangler4.125.0@cloudflare/workerd-linux-641.20260820.1miniflare5.20260820.0-alpha@opennextjs/cloudflarewrangler devservers running concurrently on the same runnerPlease provide a link to a minimal reproduction
I do not have one, and I would rather say so than link something that does not reproduce.
The trigger is environmental — a connection dropped mid-response, which we only ever see under contention on a small runner. But the defect itself is in
wrangler's own source and does not depend on our code: the path below produces an emptyErrorunconditionally, for any project. The analysis is source-level, and I have included the exact code so it can be checked without running anything.Happy to provide more logs or to test a patch.
Describe the Bug
wrangler devexits mid-session with:The
[ERROR]line carries no message at all. The real one is in the log file, at debug level:So the information exists. It is simply not on the line a user reads.
Why the printed error is empty.
ProxyWorker.jsposts the failure to the controller as a plain object, because anErrorcannot cross the worker boundary:cli.jsthen reconstructs it:The posted value is never an
Error, so this path always yields anErrorwith an empty message.emitErrorEventdispatches it, and the dev server stops.wrangler already treats two neighbouring conditions as non-fatal.
DevEnv.handleErrorEventhas an exemption branch, and it is two reasons wide:The design already recognises that a ProxyController error can be transient and should only be logged.
Error inside ProxyWorker— a rejected proxy fetch — is the same kind of condition, and falls through to the fatalelse. There is no configuration escape: the fatality is hardcoded.Why it matters beyond a missing message. The rejection is transient: one connection cut mid-response is enough. A single one takes the whole dev server down, and every request after it fails. In our end to end suite that is 169 tests failing for one dropped connection, with nothing on the console to say why.
Two suggestions, in order of value:
castErrorCausecould rebuild a realErrorfrom the posted shape, which already carriesname,messageandstack. The information exists and is discarded at the exact moment a user needs it.Error inside ProxyWorkercould join the exemption branch above. It is a transient network condition, not a configuration fault, and the code already makes that distinction for its two neighbours.Please provide any relevant error logs
Observed 8 times over 31 CI runs. The crash always lands on the same test, and the number of failures after it is identical every time. Never reproduced on a developer machine, where the same suite passes.
One data point that may help you place it: after splitting our suite so each job runs one server instead of two, the crash has not recurred — 2 runs, which we do not consider proof. That is consistent with contention producing the dropped connection. The dropped connection being fatal is the part being reported here.