From 9fb4482aa52bbf6e751b50f57cf57dd6623be817 Mon Sep 17 00:00:00 2001 From: HughhhhCoder Date: Thu, 27 Aug 2026 14:17:37 +0800 Subject: [PATCH 1/2] Preserve caller cancellation during cloud publication --- sdk/typescript/src/cloud-publish.ts | 6 ++- sdk/typescript/tests-ts/cloud-publish.test.ts | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/sdk/typescript/src/cloud-publish.ts b/sdk/typescript/src/cloud-publish.ts index 3f0a6ce63..8f5a1b3ee 100644 --- a/sdk/typescript/src/cloud-publish.ts +++ b/sdk/typescript/src/cloud-publish.ts @@ -455,6 +455,7 @@ async function publishCloudPayload( signal, }); } catch { + dependencies.signal?.throwIfAborted(); // A lost response does not establish whether the server accepted the POST. throw new CodexSecurityError( "Cloud publication was not confirmed. The request was not retried; check whether it was accepted before submitting again.", @@ -475,7 +476,10 @@ async function publishCloudPayload( ); } const receipt = receiptSchema.safeParse( - await response.json().catch(() => undefined), + await response.json().catch(() => { + dependencies.signal?.throwIfAborted(); + return undefined; + }), ); // Cloud assigns opaque IDs in request order, so they cannot be compared to // local finding IDs. The authenticated response must still preserve the diff --git a/sdk/typescript/tests-ts/cloud-publish.test.ts b/sdk/typescript/tests-ts/cloud-publish.test.ts index 200b97613..a5c39f86d 100644 --- a/sdk/typescript/tests-ts/cloud-publish.test.ts +++ b/sdk/typescript/tests-ts/cloud-publish.test.ts @@ -684,6 +684,58 @@ describe("Cloud publication", () => { expect(requests).toBe(1); }); + test("preserves caller cancellation during the publication request", async () => { + const { scan, environment } = await fixture(); + const controller = new AbortController(); + const cancellation = new Error("publication cancelled"); + + await expect( + publishScanToCloud(scan, { + environment, + signal: controller.signal, + fetch: async (_url, options) => { + const signal = options.signal as AbortSignal; + return await new Promise((_resolve, reject) => { + signal.addEventListener( + "abort", + () => reject(signal.reason), + { once: true }, + ); + controller.abort(cancellation); + }); + }, + }), + ).rejects.toBe(cancellation); + }); + + test("preserves caller cancellation while parsing the acceptance receipt", async () => { + const { scan, environment } = await fixture(); + const controller = new AbortController(); + const cancellation = new Error("receipt parsing cancelled"); + const response = { + ok: true, + status: 201, + body: null, + json: async () => + await new Promise((_resolve, reject) => { + controller.signal.addEventListener( + "abort", + () => reject(controller.signal.reason), + { once: true }, + ); + controller.abort(cancellation); + }), + } as unknown as Response; + + await expect( + publishScanToCloud(scan, { + environment, + signal: controller.signal, + fetch: async () => response, + }), + ).rejects.toBe(cancellation); + }); + test("requires a complete acceptance receipt instead of treating any 2xx as success", async () => { const { scan, environment } = await fixture(); for (const body of [ From d2e60bf93d64a858458c648a6574a63c5e79a81e Mon Sep 17 00:00:00 2001 From: HughhhhCoder Date: Thu, 27 Aug 2026 19:01:05 +0800 Subject: [PATCH 2/2] style: format cloud publication regression test --- sdk/typescript/tests-ts/cloud-publish.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk/typescript/tests-ts/cloud-publish.test.ts b/sdk/typescript/tests-ts/cloud-publish.test.ts index a5c39f86d..9cd96f6ca 100644 --- a/sdk/typescript/tests-ts/cloud-publish.test.ts +++ b/sdk/typescript/tests-ts/cloud-publish.test.ts @@ -696,11 +696,9 @@ describe("Cloud publication", () => { fetch: async (_url, options) => { const signal = options.signal as AbortSignal; return await new Promise((_resolve, reject) => { - signal.addEventListener( - "abort", - () => reject(signal.reason), - { once: true }, - ); + signal.addEventListener("abort", () => reject(signal.reason), { + once: true, + }); controller.abort(cancellation); }); },