From 1301400fdde7eb89370537af3531122c2e146fc9 Mon Sep 17 00:00:00 2001 From: Gary King Date: Tue, 25 Nov 2025 17:13:52 -0500 Subject: [PATCH 1/2] fix script hanging for 30 seconds --- src/utils/fetch-with-retry.ts | 38 +++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/utils/fetch-with-retry.ts b/src/utils/fetch-with-retry.ts index 1d348a51..7f298a12 100644 --- a/src/utils/fetch-with-retry.ts +++ b/src/utils/fetch-with-retry.ts @@ -36,9 +36,16 @@ function headersToObject(headers: Headers): Record { } /** - * Creates an AbortSignal that times out after the specified duration + * Creates an AbortSignal that aborts after timeoutMs. Returns the signal and a + * clear function to cancel the timeout early. */ -function createTimeoutSignal(timeoutMs: number, existingSignal?: AbortSignal): AbortSignal { +function createTimeoutSignal( + timeoutMs: number, + existingSignal?: AbortSignal, +): { + signal: AbortSignal + clear: () => void +} { const controller = new AbortController() // Timeout logic @@ -46,6 +53,8 @@ function createTimeoutSignal(timeoutMs: number, existingSignal?: AbortSignal): A controller.abort(new Error(`Request timeout after ${timeoutMs}ms`)) }, timeoutMs) + const clear = () => clearTimeout(timeoutId) + // If there's an existing signal, forward its abort if (existingSignal) { if (existingSignal.aborted) { @@ -68,7 +77,7 @@ function createTimeoutSignal(timeoutMs: number, existingSignal?: AbortSignal): A clearTimeout(timeoutId) }) - return controller.signal + return { signal: controller.signal, clear } } /** @@ -104,11 +113,17 @@ export async function fetchWithRetry(args: { let lastError: Error | undefined for (let attempt = 0; attempt <= config.retries; attempt++) { + // Timeout clear function for this attempt (hoisted for catch scope) + let clearTimeoutFn: (() => void) | undefined + try { // Set up timeout and signal handling let requestSignal = userSignal || undefined if (timeout && timeout > 0) { - requestSignal = createTimeoutSignal(timeout, requestSignal) + const timeoutResult = createTimeoutSignal(timeout, requestSignal) + + requestSignal = timeoutResult.signal + clearTimeoutFn = timeoutResult.clear } // Use custom fetch or native fetch @@ -176,6 +191,11 @@ export async function fetchWithRetry(args: { data = responseText as T } + // Success – clear pending timeout (if any) so Node can exit promptly + if (clearTimeoutFn) { + clearTimeoutFn() + } + return { data, status: fetchResponse.status, @@ -194,6 +214,11 @@ export async function fetchWithRetry(args: { const networkError = lastError networkError.isNetworkError = true } + + if (clearTimeoutFn) { + clearTimeoutFn() + } + throw lastError } @@ -202,6 +227,11 @@ export async function fetchWithRetry(args: { if (delay > 0) { await new Promise((resolve) => setTimeout(resolve, delay)) } + + // Retry path – ensure this attempt's timeout is cleared before looping + if (clearTimeoutFn) { + clearTimeoutFn() + } } } From 59435ae655a854f253e81f772e2f72a308ab2ed0 Mon Sep 17 00:00:00 2001 From: Gary King Date: Wed, 26 Nov 2025 10:17:26 -0500 Subject: [PATCH 2/2] change to a func decl --- src/utils/fetch-with-retry.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/fetch-with-retry.ts b/src/utils/fetch-with-retry.ts index 7f298a12..dcc8c2a7 100644 --- a/src/utils/fetch-with-retry.ts +++ b/src/utils/fetch-with-retry.ts @@ -53,7 +53,9 @@ function createTimeoutSignal( controller.abort(new Error(`Request timeout after ${timeoutMs}ms`)) }, timeoutMs) - const clear = () => clearTimeout(timeoutId) + function clear() { + clearTimeout(timeoutId) + } // If there's an existing signal, forward its abort if (existingSignal) {