Skip to content

[wrangler] Fail with an error instead of hanging under Bun - #15846

Merged
petebacondarwin merged 15 commits into
cloudflare:mainfrom
oddharsh:oddharsh/bun-harness-keep-getworker
Oct 19, 2026
Merged

petebacondarwin merged 15 commits into
cloudflare:mainfrom
oddharsh:oddharsh/bun-harness-keep-getworker

Conversation

@oddharsh

@oddharsh oddharsh commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #15717.

Under Bun, Wrangler's local dev requests hang with no error, because Bun's fetch() ignores the undici dispatcher that Miniflare routes them through (oven-sh/bun#39247). The real fix belongs in Bun. This PR adds one isBun() check at each place a user would otherwise wait forever, and each check throws or responds with an error that names the Bun issue:

  • createTestHarness(): the dispatchFetch() helper behind server.fetch() and getWorker()'s fetch(), email() and scheduled() throws a UserError. getEnv() and getExport() don't dispatch, so they keep working.
  • The dev server URL: ProxyController passes the error text to the ProxyWorker as a binding, and the ProxyWorker answers with a 503 instead of queueing the request.
  • Remote bindings: startRemoteProxySession() throws a UserError before starting the session.

This builds on #15845 and includes its commit under its original author. #15845 fails in listen(), which also blocks getEnv().


  • Tests
    • Tests included/updated: the harness test simulates Bun under Node by mocking isBun(), and fails on a 200 without the ProxyWorker change. On Bun 1.4.4 with this branch built, getEnv() works, both fetch() calls throw the UserError, and the dev server URL answers 503; Node is unchanged. Remote bindings were not run against a live session under Bun.
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: error handling only; the server.fetch() JSDoc describes the Bun behaviour

Note

This is a contribution from an AI agent: Claude Code, running Claude Opus 5.5. It was written on instructions from @oddharsh and under their supervision, and @oddharsh reviewed the change and this description before submission.

@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 617272b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
wrangler Patch
@cloudflare/vite-plugin Patch
@cloudflare/vitest-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@15846

@cloudflare/build-output-utils

npm i https://pkg.pr.new/@cloudflare/build-output-utils@15846

@cloudflare/codemods

npm i https://pkg.pr.new/@cloudflare/codemods@15846

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@15846

@cloudflare/containers-shared

npm i https://pkg.pr.new/@cloudflare/containers-shared@15846

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@15846

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@15846

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@15846

miniflare

npm i https://pkg.pr.new/miniflare@15846

@cloudflare/pages-functions

npm i https://pkg.pr.new/@cloudflare/pages-functions@15846

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@15846

@cloudflare/runtime-types

npm i https://pkg.pr.new/@cloudflare/runtime-types@15846

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@15846

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@15846

@cloudflare/vitest-plugin

npm i https://pkg.pr.new/@cloudflare/vitest-plugin@15846

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@15846

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@15846

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@15846

wrangler

npm i https://pkg.pr.new/wrangler@15846

commit: 617272b

@oddharsh
oddharsh marked this pull request as ready for review September 24, 2026 19:03
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@oddharsh

oddharsh commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

#15906 changed this PR's premise. Under bun, getWorker().fetch() no longer reaches the Worker: a bare path answers 404, a made-up host fails DNS, and a real host gets the request itself (measurements in #15717 (comment)). This PR keeps that path open and its error message points users at it, which is now the wrong advice. I'm moving it to draft while I revise it along the lines of that comment.

@oddharsh
oddharsh marked this pull request as draft October 2, 2026 12:34
@oddharsh
oddharsh marked this pull request as ready for review October 7, 2026 12:43
@workers-devprod
workers-devprod requested review from a team and jamesopstad and removed request for a team October 7, 2026 12:44
@workers-devprod

workers-devprod commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler
Show detailed file reviewers
  • .changeset/fuzzy-bears-listen.md: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/api/startDevWorker/ProxyController.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/api/test-harness.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/tests/utils/is-bun.test.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/startDevWorker/ProxyController.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/test-harness-runtime.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/api/test-harness.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/utils/is-bun.ts: [@cloudflare/wrangler]
  • packages/wrangler/src/utils/logPossibleBugMessage.ts: [@cloudflare/wrangler]
  • packages/wrangler/templates/startDevWorker/ProxyWorker.ts: [@cloudflare/wrangler]

@workers-devprod
workers-devprod requested review from a team October 7, 2026 12:56
dario-piotrowicz and others added 4 commits October 7, 2026 09:43
… doors

The guard in cloudflare#15845 ran inside listen(), so under Bun it also refused
server.getWorker().fetch(), which never goes through the ProxyWorker and
worked before. Move the check to server.fetch(), and have the ProxyWorker
answer with a 503 once a control request arrives without its payload, so
requests to the listen() URL fail loudly instead of queueing forever.
…er the harness under simulated Bun

A request already forwarded to the UserWorker sits in neither queue when an
empty control request drains them, and its retry is requeued asynchronously
once the old connection fails. Route both requeue sites through
requeueForRetry(), which answers with the same 503 while
controlPayloadMissing is set instead of waiting for a play that cannot come.

Add a createTestHarness() test that simulates Bun under Node (isBun() true,
proxy control requests stripped of cf) and asserts listen() and
getWorker().fetch() work while server.fetch() throws and the listen() URL
answers 503.
@oddharsh
oddharsh force-pushed the oddharsh/bun-harness-keep-getworker branch from f874268 to 4712825 Compare October 7, 2026 13:46
@oddharsh
oddharsh changed the base branch from dario/bun-testharness-listen-fix to main October 7, 2026 13:46
oddharsh and others added 2 commits October 7, 2026 10:39
…orkflows-shared

Both are HeadersTimeoutError / waitUntil timeouts on the Windows runners. The same fixture ran 17/17 on Windows at this branch's previous head, and an unrelated PR failed it with the same HeadersTimeoutError.
…ing every binding request

startRemoteProxySession reported ready once the remote Worker reloaded,
but the `play` message that hands the ProxyWorker that session was sent
in the background. Under Bun it never arrives: Miniflare's dispatchFetch()
routes it through an undici dispatcher that Bun's fetch() ignores, so it
goes to the literal http://dummy/ URL and fails with ENOTFOUND. The error
was emitted after startup stopped listening, and the ProxyWorker queued
every remote binding request forever, as reported for
getPlatformProxy({ remoteBindings: true }) in cloudflare#15717.

ProxyController now records whether `play` was delivered, and startup
waits for it. Under Bun a failed delivery throws a UserError naming
oven-sh/bun#39247; elsewhere it goes through the existing startup error
path.
…ctly under Bun

Since cloudflare#15906, Miniflare rewrites dispatchFetch()'s URL onto the
ProxyWorker's origin inside the undici dispatcher, which Bun's fetch()
ignores. Under Bun a control request now goes to the literal http://dummy/
URL and fails, so it never reaches the ProxyWorker, and the 503 this PR
gives requests to the listen() URL never fired: on Bun 1.4.4 those
requests hung again (no answer after 15 s), while server.fetch() still
threw its UserError.

When every retry of a control request fails under Bun, ProxyController
now sends one without a payload straight to the ProxyWorker's own URL,
which Bun can reach. The ProxyWorker's existing handling for a missing
payload then answers queued and later requests with the 503. Measured on
Bun 1.4.4 with wrangler built from this branch: fetch(listenUrl) gets
the 503 naming oven-sh/bun#39247; Node is unchanged.
@workers-devprod

workers-devprod commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@clanky-the-clanker

Copy link
Copy Markdown

This pull request was not reviewed automatically because its author does not have write access to this repository. A maintainer with write access can start a review by commenting clanky review (or clanker review or @clanky-the-clanker review), optionally followed by instructions (e.g. clanky review focus on the retry logic).

devin-ai-integration[bot]

This comment was marked as resolved.

…y after updateBindings

DevEnv only logs a failed `play` send and never re-emits it, so an error
event that arrives while startup waits for `play` is a separate failure
(authentication, the remote preview). Startup now reports it as itself
even under Bun, and keeps the Bun-specific error for a `play` that was not
delivered. ProxyController records that send failure in `latestSendError`,
since the event bus never carried it; the test mock emitted it as an error
event, which the real DevEnv does not, so the mock now matches DevEnv.

updateBindings now waits for the new `play` to be acknowledged, as startup
does, instead of only for the message mutex to drain.

The changeset lists the Vite and Vitest plugins, which bundle the private
@cloudflare/remote-bindings package.
Wrangler's rule is undici's fetch, never the global one. Wrangler bundles
undici, so its fetch and dispatcher work under Bun (only Miniflare's
runtime `undici` import resolves to Bun's dispatcher-less shim). Measured
on Bun 1.4.4 with undici 7.29.1 inlined by esbuild: an Agent with
rejectUnauthorized false reaches a self-signed HTTPS server (204), and the
same request without it fails with DEPTH_ZERO_SELF_SIGNED_CERT.
devin-ai-integration[bot]

This comment was marked as resolved.

…cribes an older message

A non-204 answer, or a ProxyWorker that was not ready, returned false
without touching latestSendError, so a failure left over from an earlier
message was reported as the cause of a later rejected `play`. Each send now
sets latestSendError to its own outcome: cleared on a 204, a status error
on any other answer.
…ker-node

Both files hit node --test's 15 s timeout on Test partition (Windows, fixtures, 1) while the dev server answered GET / 200 OK after 9117 ms. The fixture passed 5/5 on Windows at 63eb034 and e0a5086, and d3a474e only changes when remote-bindings sets latestSendError.
@jamesopstad
jamesopstad requested review from petebacondarwin and removed request for jamesopstad October 19, 2026 10:36

@petebacondarwin petebacondarwin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can you clean up the PR description here?
We don't need to know the history of changes to the PR. Instead a short clear description of what this PR achieves and why.

In general we don't land PRs to improve Bun compatibility - any compatibility issue is deemed on Bun to fix.

In this case it appears that yo are just trying to provide better error messages when Bun fails. We could consider this although as it stands the PR is quite complicated and are a loathe to add such logic just for Bun.

@github-project-automation github-project-automation Bot moved this from Untriaged to In Review in workers-sdk Oct 19, 2026
oddharsh and others added 2 commits October 19, 2026 12:56
Each Bun failure now gets one check at its entry point: the harness's dispatchFetch() helper throws a UserError, the ProxyWorker answers with a 503 when ProxyController passes it the Bun message as a binding, and startRemoteProxySession() throws a UserError. This drops the play-delivery tracking in both ProxyController copies, the ProxyWorker's missing-payload state, the undici fallback request and the harness timeout.
@oddharsh oddharsh changed the title [wrangler] Keep getWorker() working under Bun and fail only the proxy doors [wrangler] Fail with an error instead of hanging under Bun Oct 19, 2026
@oddharsh

Copy link
Copy Markdown
Contributor Author

Ah for sure, so sorry about the wordiness on the description before - I'd assumed it'd gotten a bit lost as the PR morphed. Also totally understand with regards to not going out of our way to write this repo around Bun! I’ve rewritten the description and cut the change to error messages only: one isBun() check (Dario’s helper from #15845) wherever a request would otherwise hang. The diff went from 963 lines across 16 files to 177 across 10 (617272b). Happy to drop the remote-bindings check too if you’d rather keep this to the test harness!

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment thread packages/remote-bindings/src/start-remote-proxy-session.ts
@petebacondarwin
petebacondarwin enabled auto-merge (squash) October 19, 2026 20:47

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from In Review to Approved in workers-sdk Oct 19, 2026
@petebacondarwin
petebacondarwin merged commit a88cac4 into cloudflare:main Oct 19, 2026
92 of 94 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[wrangler] createTestHarness() under Bun: listen() succeeds, then server.fetch() and every request to the returned URL hang forever with no error

4 participants