From a6ac8de52399396d21b8acaf7af30aecc535a157 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 19 Jun 2026 09:53:30 +0200 Subject: [PATCH] fix(openai): cancel OAuth preflight bodies --- .../openai/openai-chatgpt-oauth.runtime.test.ts | 15 +++++++++++++++ extensions/openai/openai-chatgpt-oauth.runtime.ts | 7 ++++++- src/commands/oauth-tls-preflight.test.ts | 9 +++++---- src/plugins/provider-openai-chatgpt-oauth-tls.ts | 7 ++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/extensions/openai/openai-chatgpt-oauth.runtime.test.ts b/extensions/openai/openai-chatgpt-oauth.runtime.test.ts index b48a923c6bc..6ac2fd614e7 100644 --- a/extensions/openai/openai-chatgpt-oauth.runtime.test.ts +++ b/extensions/openai/openai-chatgpt-oauth.runtime.test.ts @@ -22,4 +22,19 @@ describe("OpenAI Codex OAuth runtime", () => { expect(timeoutSpy).toHaveBeenCalledWith(MAX_TIMER_TIMEOUT_MS); expect(fetchImpl).toHaveBeenCalledTimes(1); }); + + it("cancels reachable TLS preflight response bodies", async () => { + const response = new Response("reachable", { status: 302 }); + const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined); + const fetchImpl = vi.fn(async () => response); + + await expect( + testing.runOpenAIOAuthTlsPreflight({ + timeoutMs: 20, + fetchImpl, + }), + ).resolves.toEqual({ ok: true }); + + expect(cancel).toHaveBeenCalledOnce(); + }); }); diff --git a/extensions/openai/openai-chatgpt-oauth.runtime.ts b/extensions/openai/openai-chatgpt-oauth.runtime.ts index b73c476cf81..747192ccd22 100644 --- a/extensions/openai/openai-chatgpt-oauth.runtime.ts +++ b/extensions/openai/openai-chatgpt-oauth.runtime.ts @@ -94,8 +94,9 @@ async function runOpenAIOAuthTlsPreflight(options?: { }): Promise { const timeoutMs = resolveTimerTimeoutMs(options?.timeoutMs, 5000); const fetchImpl = options?.fetchImpl ?? fetch; + let response: Response | undefined; try { - await fetchImpl(openAIAuthProbeUrl, { + response = await fetchImpl(openAIAuthProbeUrl, { method: "GET", redirect: "manual", signal: AbortSignal.timeout(timeoutMs), @@ -109,6 +110,10 @@ async function runOpenAIOAuthTlsPreflight(options?: { code: failure.code, message: failure.message, }; + } finally { + if (response?.bodyUsed !== true) { + await response?.body?.cancel().catch(() => undefined); + } } } diff --git a/src/commands/oauth-tls-preflight.test.ts b/src/commands/oauth-tls-preflight.test.ts index 6465d5ce423..3c23901af86 100644 --- a/src/commands/oauth-tls-preflight.test.ts +++ b/src/commands/oauth-tls-preflight.test.ts @@ -1,12 +1,12 @@ // OAuth TLS preflight tests cover timeout handling, TLS diagnostics, and suggested fixes. import { MAX_TIMER_TIMEOUT_MS } from "@openclaw/normalization-core/number-coercion"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withEnv } from "../test-utils/env.js"; import { formatOpenAIOAuthTlsPreflightFix, runOpenAIOAuthTlsPreflight, shouldRunOpenAIOAuthTlsPrerequisites, } from "../plugins/provider-openai-chatgpt-oauth-tls.js"; +import { withEnv } from "../test-utils/env.js"; describe("runOpenAIOAuthTlsPreflight", () => { beforeEach(() => { @@ -14,11 +14,12 @@ describe("runOpenAIOAuthTlsPreflight", () => { }); it("returns ok when OpenAI auth endpoint is reachable", async () => { - const fetchImpl = vi.fn( - async () => new Response("", { status: 400 }), - ) as unknown as typeof fetch; + const response = new Response("reachable", { status: 400 }); + const cancel = vi.spyOn(response.body!, "cancel").mockResolvedValue(undefined); + const fetchImpl = vi.fn(async () => response) as unknown as typeof fetch; const result = await runOpenAIOAuthTlsPreflight({ fetchImpl, timeoutMs: 20 }); expect(result).toEqual({ ok: true }); + expect(cancel).toHaveBeenCalledOnce(); }); it("caps oversized probe timeouts before creating abort signals", async () => { diff --git a/src/plugins/provider-openai-chatgpt-oauth-tls.ts b/src/plugins/provider-openai-chatgpt-oauth-tls.ts index 2299e3ea9ed..dc9f245428e 100644 --- a/src/plugins/provider-openai-chatgpt-oauth-tls.ts +++ b/src/plugins/provider-openai-chatgpt-oauth-tls.ts @@ -105,8 +105,9 @@ export async function runOpenAIOAuthTlsPreflight(options?: { }): Promise { const timeoutMs = resolveTimerTimeoutMs(options?.timeoutMs, 5000); const fetchImpl = options?.fetchImpl ?? fetch; + let response: Response | undefined; try { - await fetchImpl(OPENAI_AUTH_PROBE_URL, { + response = await fetchImpl(OPENAI_AUTH_PROBE_URL, { method: "GET", redirect: "manual", signal: AbortSignal.timeout(timeoutMs), @@ -120,6 +121,10 @@ export async function runOpenAIOAuthTlsPreflight(options?: { code: failure.code, message: failure.message, }; + } finally { + if (response?.bodyUsed !== true) { + await response?.body?.cancel().catch(() => undefined); + } } }