Skip to content

[vite-plugin] Route v1 DevTools WebSocket upgrades explicitly - #16538

Merged
petebacondarwin merged 1 commit into
codex/vite-v1-backport-16403from
codex/vite-v1-backport-16519
Oct 28, 2026
Merged

petebacondarwin merged 1 commit into
codex/vite-v1-backport-16403from
codex/vite-v1-backport-16519

Conversation

@petebacondarwin

@petebacondarwin petebacondarwin commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by #16540, which places this fix directly on #16504 as requested.

While reordering, I pushed the rebased fix into this PR's old base branch (#16444) before changing its base. GitHub automatically marked this PR merged into that temporary branch and deleted its head. The vite-plugin-v1 target was never updated. GitHub rejects changing the base of a merged PR; #16540 preserves the same fix, tests and existing changeset in the correct position.

Original description:

Backports #16519 to Vite plugin v1, stacked on #16444 and the existing v1 backports.

Vite DevTools upgrades at /__devtools/__ws must reach its own listener without being dispatched to the Worker. Replace the inherited write-count heuristic from #16504 with explicit routing, preserving Sandbox forwarding. This also fixes a legitimate Worker upgrade being rejected when an earlier pipelined HTTP response finishes during dispatch. The original source, socket regressions, and existing changeset apply directly; no dependency upgrade or additional changelog entry is needed.

Validation: Node 22.22.1 / pnpm 10.33.0; pnpm build, pnpm check, pnpm test:ci, and the WebSockets playground in dev and built preview.


  • 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: this fixes existing v1 WebSocket routing without changing its public API.

Note

This is a contribution from an AI agent: Codex, under the authorized v1 backport automation.


Devin Review

@changeset-bot

changeset-bot Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 3ca16f5

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 1 package
Name Type
@cloudflare/vite-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

@petebacondarwin

Copy link
Copy Markdown
Contributor Author

@petebacondarwin (original author) @dario-piotrowicz (human ant-core approver): please check this v1 backport of #16519 and approve it if it looks good. The original routing fix, real-socket tests and existing changeset apply directly on the v1 stack; #16444 supplies the earlier runtime dependencies. Build, checks, retained tests and WebSockets dev/preview pass.

@workers-devprod
workers-devprod requested review from a team and cjol and removed request for a team October 28, 2026 11:03
@workers-devprod

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • @cloudflare/wrangler

@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 2 potential issues.

Devin Review

Comment on lines 81 to +82
if (!workerWebSocket) {
// This listener does not own the socket. Destroy it only if no
// other listener has written a response in the meantime.
if (!isClaimed()) {
socket.destroy();
}
socket.destroy();

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.

🔴 Other plugins' WebSocket connections break

When another listener upgrades a non-DevTools path, handleWebSocket still dispatches it to the Worker. A non-WebSocket response destroys the claimed socket; a WebSocket response attempts a second handshake.

Learn more

Node invokes every upgrade listener for an incoming request. A Vite plugin can register its own listener for /custom-ws before this handler. The removed claimed-socket check protected an already-upgraded socket when the Worker later returned no WebSocket, and also prevented a second handshake when it did return one. Merely reserving the DevTools path does not protect other listeners. The socket is now destroyed or upgraded twice depending on the Worker response.

Example: A plugin accepts /custom-ws and sends a 101 response. The Worker returns 404 for /custom-ws; this handler calls socket.destroy() and closes the plugin's live connection.

Recommended fix: Restore an ownership check for non-reserved paths before destroying or upgrading a socket. Distinguish an earlier HTTP response on a pipelined connection from another listener's handshake, since the removed byte-count check rejected valid Worker upgrades in that case. Test a second listener on a non-DevTools path with both Worker response types.

Devin Review


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

Comment on lines +65 to 66
if ((isViteRequest || isDevToolsRequest) && !isSandboxRequest) {
return;

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.

🟡 Preview WebSocket route hangs without a handler

In preview, handleWebSocket skips /__devtools/__ws despite no DevTools listener handling that path. A Worker WebSocket at that path never receives the upgrade, leaving its client waiting.

Learn more

The same handleWebSocket function is installed by both devPlugin and previewPlugin. A preview server does not have the Vite DevTools handler that this pathname exemption expects. Returning from the only relevant upgrade listener does not send an HTTP response or close the socket. Previously the Worker received this request and could return a WebSocket.

Example: A Worker handles /__devtools/__ws with a WebSocket response. Connecting to that path on vite preview now receives neither a 101 response nor the Worker's messages.

Recommended fix: Scope the DevTools exemption to development servers where the DevTools upgrade listener is actually present, and preserve Worker forwarding in preview. Add a preview-specific upgrade test.

Devin Review


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

@pkg-pr-new

pkg-pr-new Bot commented Oct 28, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@cloudflare/vite-plugin@16538

commit: da7ec59

@clanky-the-clanker

Copy link
Copy Markdown

💬 Approved with comments — The routing change is correct and well tested. The only item is an optional suggestion to describe the pipelined-upgrade fix in the changeset.

Findings: 1 suggestion

The decision is advisory: this comment never approves, blocks or requests changes on the pull request.


🚀 Release: 💬 1 suggestion

The changeset could also mention the pipelined-upgrade fix.

Recommendation: 💬 Optional Nits

  • [SUGGESTION] The changeset describes only the DevTools routing change. Consider also mentioning the fix for Worker upgrades that were rejected when an earlier pipelined HTTP response finished during dispatch. (.changeset/witty-pandas-serve.md:7)
View full review

Risk: Low.

.changeset/witty-pandas-serve.md describes only the DevTools routing change. This pull request also fixes Worker WebSocket upgrades being rejected when an earlier pipelined HTTP response finishes during dispatch. The pull request description says the existing changeset applies unchanged. Adding a sentence about the pipelined case would still make the release notes more complete.


✅ All other checks passed

🔒 Security: ✅ LGTM

No security concerns. The only upgrades kept away from the Worker are those to the exact DevTools path, and Sandbox origins are still forwarded.


📖 AGENTS.md: ✅ LGTM

The change follows the repository conventions: it has the [vite-plugin] title prefix, a plugin changeset, and tests that use real sockets.


📋 Code Quality: ✅ LGTM

The DevTools path is matched on url.pathname, so a query string such as ?token=... still matches. Near-miss paths such as /__devtools/__ws-extra and /__devtools/__ws/child still reach the Worker.

With the bytesWritten heuristic removed, a Worker upgrade is no longer rejected when an earlier pipelined response finishes during dispatch. The failure paths now call socket.destroy() directly. The socket.destroyed check after dispatch is kept.

The tests use a real ws client and cover DevTools routing, Sandbox forwarding, near-miss paths and the ordering of a pipelined response before the upgrade.


⚡ Performance: ✅ LGTM

No performance concerns.


📝 Documentation: ✅ LGTM

No documentation changes are needed. The public API is unchanged.


Reviewed commit: da7ec59e

@petebacondarwin
petebacondarwin removed this pull request from stack #16505 October 28, 2026 11:08
@petebacondarwin
petebacondarwin force-pushed the codex/vite-v1-backport-16519 branch from da7ec59 to 3ca16f5 Compare October 28, 2026 11:08
@petebacondarwin
petebacondarwin merged commit 3eb00cd into codex/vite-v1-backport-16403 Oct 28, 2026
@petebacondarwin
petebacondarwin deleted the codex/vite-v1-backport-16519 branch October 28, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants