From 580534bf92f1bd92d286919fba007e32df2a3d7f Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Thu, 7 May 2026 21:21:03 +0800 Subject: [PATCH 1/5] test(opencode): avoid fetch teardown in built server bootstrap --- .../server/built-node-skill-bootstrap.test.ts | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/packages/opencode/test/server/built-node-skill-bootstrap.test.ts b/packages/opencode/test/server/built-node-skill-bootstrap.test.ts index c45083551..fb3670515 100644 --- a/packages/opencode/test/server/built-node-skill-bootstrap.test.ts +++ b/packages/opencode/test/server/built-node-skill-bootstrap.test.ts @@ -50,6 +50,7 @@ describe("built node server skill bootstrap", () => { expectModelsSnapshotUnchanged(modelsFixture) const script = ` + import { request as httpRequest } from "node:http" import { Server, Log } from ${JSON.stringify(pathToFileURL(distEntry).href)} const password = process.env.OPENCODE_SERVER_PASSWORD @@ -61,25 +62,51 @@ describe("built node server skill bootstrap", () => { const listener = await Server.listen({ port: 0, hostname: "127.0.0.1" }) const auth = "Basic " + Buffer.from(\`opencode:\${password}\`).toString("base64") - const request = async (pathname) => { + // This test exits the child process explicitly. On Windows Node 24, fetch + // can abort during process.exit() handle teardown and hide the bootstrap result. + const request = (pathname) => new Promise((resolve, reject) => { const url = new URL(pathname, listener.url) url.searchParams.set("directory", directory) - const response = await fetch(url, { - headers: { - authorization: auth, + + const req = httpRequest( + url, + { + agent: false, + headers: { + authorization: auth, + connection: "close", + }, + }, + (response) => { + const chunks = [] + response.on("data", (chunk) => chunks.push(chunk)) + response.on("error", reject) + response.on("end", () => { + resolve({ + status: response.statusCode ?? 0, + body: Buffer.concat(chunks).toString("utf8"), + }) + }) }, - }) + ) + + req.on("error", reject) + req.end() + }) + + const readEndpoint = async (pathname) => { + const response = await request(pathname) return { status: response.status, - body: await response.text(), + body: response.body, } } let exitCode = 0 try { const result = { - agent: await request("/agent"), - command: await request("/command"), + agent: await readEndpoint("/agent"), + command: await readEndpoint("/command"), } console.log(JSON.stringify(result)) if (result.agent.status !== 200 || result.command.status !== 200) { From bdac89a09aa46355406379959e07f0e1542662c5 Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Thu, 7 May 2026 21:37:33 +0800 Subject: [PATCH 2/5] test(opencode): close built webfetch harness connections --- .../opencode/test/server/built-node-webfetch.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/opencode/test/server/built-node-webfetch.test.ts b/packages/opencode/test/server/built-node-webfetch.test.ts index b1f1c5389..561ed16a6 100644 --- a/packages/opencode/test/server/built-node-webfetch.test.ts +++ b/packages/opencode/test/server/built-node-webfetch.test.ts @@ -82,8 +82,9 @@ describe("built node webfetch", () => { const rawOpenHTML = \`\${"<".repeat(50_000)}\` const whitespaceHTML = \`\${"\\r".repeat(50_000)}visible text\` + const sockets = new Set() const server = http.createServer((req, res) => { - res.writeHead(200, { "content-type": "text/html; charset=utf-8" }) + res.writeHead(200, { "content-type": "text/html; charset=utf-8", connection: "close" }) res.end( req.url === "/hostile.html" ? hostileHTML @@ -92,7 +93,11 @@ describe("built node webfetch", () => { : req.url === "/whitespace.html" ? whitespaceHTML : happyHTML, - ) + ) + }) + server.on("connection", (socket) => { + sockets.add(socket) + socket.on("close", () => sockets.delete(socket)) }) await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) @@ -147,8 +152,11 @@ describe("built node webfetch", () => { console.log(JSON.stringify(output)) } finally { + await Instance.disposeAll() await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())) + server.closeAllConnections?.() + for (const socket of sockets) socket.destroy() }) } From e845e0d5f19fa40e1113c2062a52d2bf0b209b1a Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Thu, 7 May 2026 21:57:48 +0800 Subject: [PATCH 3/5] test(opencode): avoid global fetch in built webfetch harness --- .../test/server/built-node-webfetch.test.ts | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/server/built-node-webfetch.test.ts b/packages/opencode/test/server/built-node-webfetch.test.ts index 561ed16a6..6378b9cfc 100644 --- a/packages/opencode/test/server/built-node-webfetch.test.ts +++ b/packages/opencode/test/server/built-node-webfetch.test.ts @@ -51,7 +51,9 @@ describe("built node webfetch", () => { const script = ` import http from "node:http" + import https from "node:https" import { Effect } from "effect" + import { FetchHttpClient } from "effect/unstable/http" import { Instance, Log, ToolRegistry } from ${JSON.stringify(pathToFileURL(distEntry).href)} if (typeof HTMLRewriter !== "undefined") { @@ -82,6 +84,49 @@ describe("built node webfetch", () => { const rawOpenHTML = \`\${"<".repeat(50_000)}\` const whitespaceHTML = \`\${"\\r".repeat(50_000)}visible text\` + // The test validates the built Node WebFetch parser path. Avoid Node + // 24 Windows global fetch teardown, which can abort during process.exit(). + const nodeFetch = (input, init = {}) => new Promise((resolve, reject) => { + const url = new URL(input instanceof Request ? input.url : input) + const client = url.protocol === "https:" ? https : http + const headers = { ...(input instanceof Request ? Object.fromEntries(input.headers) : {}), ...(init.headers ?? {}) } + const req = client.request( + url, + { + method: init.method ?? (input instanceof Request ? input.method : "GET"), + headers: { ...headers, connection: "close" }, + agent: false, + signal: init.signal, + }, + (res) => { + const chunks = [] + res.on("data", (chunk) => chunks.push(chunk)) + res.on("error", reject) + res.on("end", () => { + const responseHeaders = new Headers() + for (const [key, value] of Object.entries(res.headers)) { + if (Array.isArray(value)) { + for (const item of value) responseHeaders.append(key, item) + } else if (value !== undefined) { + responseHeaders.set(key, value) + } + } + resolve( + new Response(Buffer.concat(chunks), { + status: res.statusCode ?? 200, + statusText: res.statusMessage, + headers: responseHeaders, + }), + ) + }) + }, + ) + + req.on("error", reject) + if (init.signal?.aborted) req.destroy(init.signal.reason) + req.end(init.body) + }) + const sockets = new Set() const server = http.createServer((req, res) => { res.writeHead(200, { "content-type": "text/html; charset=utf-8", connection: "close" }) @@ -128,7 +173,7 @@ describe("built node webfetch", () => { metadata: () => Effect.void, ask: () => Effect.void, }, - ), + ).pipe(Effect.provideService(FetchHttpClient.Fetch, nodeFetch)), ) const happy = await run("/page.html") From 86d3559d026549118b6cd84fcd5667a002dadc2c Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Thu, 7 May 2026 22:14:42 +0800 Subject: [PATCH 4/5] test(opencode): simplify built bootstrap harness request calls --- .../test/server/built-node-skill-bootstrap.test.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/opencode/test/server/built-node-skill-bootstrap.test.ts b/packages/opencode/test/server/built-node-skill-bootstrap.test.ts index fb3670515..568f65ea4 100644 --- a/packages/opencode/test/server/built-node-skill-bootstrap.test.ts +++ b/packages/opencode/test/server/built-node-skill-bootstrap.test.ts @@ -94,19 +94,11 @@ describe("built node server skill bootstrap", () => { req.end() }) - const readEndpoint = async (pathname) => { - const response = await request(pathname) - return { - status: response.status, - body: response.body, - } - } - let exitCode = 0 try { const result = { - agent: await readEndpoint("/agent"), - command: await readEndpoint("/command"), + agent: await request("/agent"), + command: await request("/command"), } console.log(JSON.stringify(result)) if (result.agent.status !== 200 || result.command.status !== 200) { From bc2434fd8ee7b9e795556c1e5f842a50aed5b55d Mon Sep 17 00:00:00 2001 From: Yuhan Lei Date: Thu, 7 May 2026 22:36:20 +0800 Subject: [PATCH 5/5] test(opencode): normalize webfetch harness init headers --- .../opencode/test/server/built-node-webfetch.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/opencode/test/server/built-node-webfetch.test.ts b/packages/opencode/test/server/built-node-webfetch.test.ts index 6378b9cfc..a09073b80 100644 --- a/packages/opencode/test/server/built-node-webfetch.test.ts +++ b/packages/opencode/test/server/built-node-webfetch.test.ts @@ -89,7 +89,16 @@ describe("built node webfetch", () => { const nodeFetch = (input, init = {}) => new Promise((resolve, reject) => { const url = new URL(input instanceof Request ? input.url : input) const client = url.protocol === "https:" ? https : http - const headers = { ...(input instanceof Request ? Object.fromEntries(input.headers) : {}), ...(init.headers ?? {}) } + const normalizeHeaders = (headers) => { + if (!headers) return {} + if (headers instanceof Headers || Array.isArray(headers)) return Object.fromEntries(headers) + if (typeof headers[Symbol.iterator] === "function") return Object.fromEntries(headers) + return headers + } + const headers = { + ...(input instanceof Request ? Object.fromEntries(input.headers) : {}), + ...normalizeHeaders(init.headers), + } const req = client.request( url, {