Skip to content

[wrangler] Keep getWorker() working under Bun and fail only the proxy doors - #15846

Open
oddharsh wants to merge 3 commits into
cloudflare:dario/bun-testharness-listen-fixfrom
oddharsh:oddharsh/bun-harness-keep-getworker
Open

oddharsh wants to merge 3 commits into
cloudflare:dario/bun-testharness-listen-fixfrom
oddharsh:oddharsh/bun-harness-keep-getworker

Conversation

@oddharsh

@oddharsh oddharsh commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #15717.

This is stacked on #15845 and targets its branch, so it reads as a change to that PR. Merge it in or take the idea, whichever is easier.

#15845 turns the Bun hang into an error, which is the right call. It puts the guard in listen(), though, and under Bun that also refuses server.getWorker().fetch(). That door dispatches straight to Miniflare with MF-Route-Override, never touches the ProxyWorker, and works under Bun today. Our suite (bun test over createTestHarness(), calling listen() and then getWorker().getEnv()) breaks on the #15845 prerelease.

This keeps #15845's detection and moves where it fires:

Measured with this branch built, using the probe from the issue plus fetch(url):

Node 26.9 Bun 1.4.3
listen() URL URL
getWorker().fetch("/") 200 200
server.fetch("/") 200 UserError, 1ms after getWorker()
fetch(url) 200 503 with the cause in the body

On #15845 as it stands, Bun throws at listen() and never reaches the other three rows.

The new ProxyController test boots a real ProxyWorker under Node and sends a control request the way Bun delivers it: Authorization set, no cf. As a control, the same test against #15845's ProxyWorker times out at 15s, which reproduces the original hang without needing Bun. Two more tests came out of review:

  • A request already forwarded when the empty control request lands is in neither queue, and its retry is requeued later. requeueForRetry() now gives it the 503 too. The test drops the connection of an in-flight request after a pause and an empty control request; without the fix it hangs to the 15s timeout.
  • A createTestHarness() test simulates Bun under Node (isBun() true, control requests stripped of cf) and asserts all four doors from the table. It fails against Fail early when createTestHarness() cannot start its proxy under Bun #15845's harness, and it also fails if the cf stripping is removed, so the simulation is what makes it pass.

pnpm check:lint, pnpm -F wrangler check:type (templates included) and pnpm check:format all pass, as do all 14 test files under src/__tests__/api.


  • Tests
    • Tests included/updated
    • 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 now describes the Bun behaviour

… 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.
@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: f315b87

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

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

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a 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: f315b87

devin-ai-integration[bot]

This comment was marked as resolved.

…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.

@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 on lines +69 to +70
if (this.controlPayloadMissing) {
return controlPayloadMissingResponse();

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.

🔴 Listening URL hangs when control disappears

When a control request never reaches the proxy, listen() returns a URL whose requests wait indefinitely. controlPayloadMissing only changes on an arriving request, so no 503 drains the queue.

Learn more

The proxy holds incoming URL requests in requestQueue until it receives a play control request. sendMessageToProxyWorker can finish unsuccessfully without delivering any control request. Since listen() no longer waits for its acknowledgment, the session and listening URL remain available in that case. The new flag is set only inside processProxyControllerRequest, which cannot run if the control request never arrives. The URL therefore keeps pending requests in the queue with no response; the Bun guard on server.fetch() does not cover direct requests to that URL.

Example: A Bun session completes its Worker reload, but all attempts to deliver play fail before reaching the ProxyWorker. listen() returns its URL; fetch(url) enters requestQueue and waits forever instead of returning the advertised 503.

Recommended fix: Handle unsuccessful or timed-out control delivery for requests arriving over the listening URL, including the case where no control message enters the Durable Object. Add coverage that drops the entire control request, not just its cf payload.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changed: a control request that never reaches the ProxyWorker is a pre-existing path on main for every runtime (after 4 attempts DevEnv.handleErrorEvent logs Failed to send message to ProxyWorker at debug and the URL waits), while Bun's failure is the request arriving without cf (measured: it gets the 400), which this PR covers; a queue timeout would also break requests held across slow rebuilds in wrangler dev, so I'd leave it to a separate change if maintainers want one.

🤖 Addressed by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants