From fa1508c2f704faeb6476d81ff735cf0ee60f026c Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Sat, 18 Jul 2026 21:16:47 +0300 Subject: [PATCH] fix(sdk): properly handle empty response body Certain HTTP requests, for example `/upload/finish`, do NOT return any body. We should handle this gracefully and avoid re-trying requests. --- src/_internalUtils.ts | 5 +++-- src/fetchTestDurations.ts | 10 ++++++++-- src/githubOIDC.ts | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/_internalUtils.ts b/src/_internalUtils.ts index c8724bc..362f1e7 100644 --- a/src/_internalUtils.ts +++ b/src/_internalUtils.ts @@ -43,10 +43,11 @@ async function fetchOk(input: RequestInfo | URL, init?: RequestInit): Promise(input: RequestInfo | URL, init?: RequestInit, backoff: number[] = HTTP_BACKOFF): Promise { +export async function getJSON(input: RequestInfo | URL, init?: RequestInit, backoff: number[] = HTTP_BACKOFF): Promise { return await retryWithBackoff(async () => { const response = await fetchOk(input, init); - return await response.json() as T; + const text = await response.text(); + return text.trim() ? JSON.parse(text) as T : undefined; }, backoff); } diff --git a/src/fetchTestDurations.ts b/src/fetchTestDurations.ts index dd415c3..11ba535 100644 --- a/src/fetchTestDurations.ts +++ b/src/fetchTestDurations.ts @@ -134,11 +134,14 @@ class TestDurationsFetcher { // would reject an empty/garbage bearer token as UNAUTHORIZED. if (token) headers['Authorization'] = `Bearer ${token}`; - return await getJSON(url, { + const result = await getJSON(url, { method: 'POST', headers, body: body ? JSON.stringify(body) : undefined, }); + if (result === undefined) + throw new Error(`Empty JSON response from ${url}`); + return result; } async fetch(): Promise { @@ -174,7 +177,10 @@ class TestDurationsFetcher { createResponse.testDurationsToken, ); - return await getJSON(submitResponse.downloadUrl, undefined, DOWNLOAD_BACKOFF); + const result = await getJSON(submitResponse.downloadUrl, undefined, DOWNLOAD_BACKOFF); + if (result === undefined) + throw new Error(`Empty JSON response from ${submitResponse.downloadUrl}`); + return result; } private async _uploadReport(data: string, uploadUrl: string) { diff --git a/src/githubOIDC.ts b/src/githubOIDC.ts index ca6c524..7e32e5b 100644 --- a/src/githubOIDC.ts +++ b/src/githubOIDC.ts @@ -58,7 +58,7 @@ export class GithubOIDC { const url = new URL(this._requestUrl); url.searchParams.set('audience', flakinessProject); - let json: { value?: string }; + let json: { value?: string } | undefined; try { json = await getJSON<{ value?: string }>(url, { headers: { @@ -70,7 +70,7 @@ export class GithubOIDC { throw new Error(`Failed to request GitHub OIDC token: ${error.message || String(error)}`); } - if (!json.value) + if (!json?.value) throw new Error('GitHub OIDC token response did not contain a token value.'); return json.value;