From 7302be9223f179bd403f4c41c7b1a21133115023 Mon Sep 17 00:00:00 2001 From: notequal <184306082+ifneq@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:10:15 -0400 Subject: [PATCH 1/4] Add retry utility with exponential backoff --- src/utils/retry.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/utils/retry.ts diff --git a/src/utils/retry.ts b/src/utils/retry.ts new file mode 100644 index 0000000..d78fcf1 --- /dev/null +++ b/src/utils/retry.ts @@ -0,0 +1,28 @@ +/** + * Retry an async function with exponential backoff. + */ +export async function retry( + fn: () => Promise, + maxAttempts: number = 3, + baseDelayMs: number = 1000 +): Promise { + let lastError: Error | undefined; + + for (let attempt = 0; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (err) { + lastError = err instanceof Error ? err : new Error(String(err)); + if (attempt < maxAttempts) { + const delay = baseDelayMs * Math.pow(2, attempt); + await sleep(delay); + } + } + } + + throw lastError; +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} From c1cbd62c83ea8cf68b3d41fe92ee44a8048fff58 Mon Sep 17 00:00:00 2001 From: notequal <184306082+ifneq@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:12:55 -0400 Subject: [PATCH 2/4] Fix off-by-one: retry maxAttempts times, not maxAttempts+1 --- src/utils/retry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/retry.ts b/src/utils/retry.ts index d78fcf1..e381a24 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -8,7 +8,7 @@ export async function retry( ): Promise { let lastError: Error | undefined; - for (let attempt = 0; attempt <= maxAttempts; attempt++) { + for (let attempt = 0; attempt < maxAttempts; attempt++) { try { return await fn(); } catch (err) { From 5b3f379d190fe0b776e325ee99c77ca2e94b69c9 Mon Sep 17 00:00:00 2001 From: notequal <184306082+ifneq@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:15:22 -0400 Subject: [PATCH 3/4] Fix unnecessary sleep after final failed attempt --- src/utils/retry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/retry.ts b/src/utils/retry.ts index e381a24..1a9bd25 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -13,7 +13,7 @@ export async function retry( return await fn(); } catch (err) { lastError = err instanceof Error ? err : new Error(String(err)); - if (attempt < maxAttempts) { + if (attempt < maxAttempts - 1) { const delay = baseDelayMs * Math.pow(2, attempt); await sleep(delay); } From 60956417d268f245124632d54ae2ef535aa8168f Mon Sep 17 00:00:00 2001 From: notequal <184306082+ifneq@users.noreply.github.com> Date: Wed, 11 Mar 2026 22:22:14 -0400 Subject: [PATCH 4/4] Validate maxAttempts >= 1 to prevent throwing undefined --- src/utils/retry.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 1a9bd25..5de066a 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -6,6 +6,10 @@ export async function retry( maxAttempts: number = 3, baseDelayMs: number = 1000 ): Promise { + if (maxAttempts < 1) { + throw new Error("maxAttempts must be at least 1"); + } + let lastError: Error | undefined; for (let attempt = 0; attempt < maxAttempts; attempt++) {