fix(cli): keep WebSocket upgrades working in the compiled binary (#61518)

This commit is contained in:
James Long 2026-11-03 17:32:22 -06:00 • committed by GitHub
parent 69ef55f02b
commit 9028e90039
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 34 additions and 0 deletions

View file

@ -114,6 +114,18 @@ const pluginRuntimePlugin: BunPlugin = {
}))
},
}
// Bun drops `export * from "ws"` in this sideEffects-free module, leaving NodeWS.WebSocketServer undefined and
// failing every WebSocket upgrade in the compiled binary. Bun's built-in ws has no PerMessageDeflate.
const wsReexportPlugin: BunPlugin = {
name: "ws-named-reexports",
setup(build) {
build.onLoad({ filter: /platform-node-shared[/\\]dist[/\\]NodeWS\.js$/ }, () => ({
contents:
'export { createWebSocketStream, extension, Receiver, Sender, subprotocol, WebSocket, WebSocketServer } from "ws"',
loader: "js",
}))
},
}
for (const item of targets) {
const opencodePty = await resolveOpencodePty({
@ -166,6 +178,7 @@ export default { path: file, version: ${JSON.stringify(opencodePty.version)}, sh
parcelWatcherPlugin,
opencodePtyPlugin,
pluginRuntimePlugin,
wsReexportPlugin,
simulationGraphPlugin,
modelsSnapshot,
],

View file

@ -2,6 +2,7 @@
import { NodeFileSystem } from "@effect/platform-node"
import { Service } from "@opencode/client/effect/service"
import { MCP_CLIENT_PATH } from "@opencode/protocol/groups/mcp"
import { ServerInfo } from "@opencode/protocol/groups/server"
import { Effect, Schema } from "effect"
import fs from "node:fs/promises"
@ -44,6 +45,7 @@ try {
const token = encodeURIComponent(credential)
const serverInfo = await waitForReady(info.url, headers)
if (serverInfo.pid !== info.pid) throw new Error("Server info does not match registration")
await verifyClientMcpSocket(info.url, token)
const tokenInfo = await fetch(new URL(`/api/info?auth_token=${token}`, info.url), {
signal: AbortSignal.timeout(5_000),
})
@ -136,6 +138,25 @@ async function verifyWebUi(url: string) {
throw new Error(`Compiled service did not serve ${script}`)
}
// Bun once bundled the WebSocket server away, so every upgrade failed only in the compiled binary.
async function verifyClientMcpSocket(url: string, token: string) {
const endpoint = new URL(`${MCP_CLIENT_PATH}?name=smoke&auth_token=${token}`, url)
endpoint.protocol = endpoint.protocol === "https:" ? "wss:" : "ws:"
const socket = new WebSocket(endpoint)
const message = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("Compiled service sent no client MCP initialize")), 5_000)
socket.onmessage = (event) => {
clearTimeout(timeout)
resolve(String(event.data))
}
socket.onerror = () => {
clearTimeout(timeout)
reject(new Error("Compiled service rejected the client MCP socket"))
}
}).finally(() => socket.close())
if (!message.includes('"initialize"')) throw new Error("Compiled service did not initialize the client MCP socket")
}
async function waitForReady(url: string, headers: HeadersInit) {
const deadline = Date.now() + 20_000
while (Date.now() < deadline) {