fix: skip retries for non-retryable errors in retryWithBackoff (#1082)

Add shouldRetry predicate to RetryOptions so callers can abort retries
for errors that will never succeed (e.g. 401 WorkflowValidationSkipError).

Previously, retryWithBackoff retried all errors blindly, wasting ~35s on
deterministic failures like workflow validation 401s.

Fixes #1081

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Andrew Grigorev
2026-04-04 20:14:47 -07:00
committed by GitHub
co-authored by Claude
parent f37c786ad3
commit d8af4e9f01
3 changed files with 132 additions and 2 deletions
+5 -2
View File
@@ -141,8 +141,11 @@ export async function setupGitHubToken(): Promise<string> {
const permissions = parseAdditionalPermissions();
console.log("Exchanging OIDC token for app token...");
const appToken = await retryWithBackoff(() =>
exchangeForAppToken(oidcToken, permissions),
const appToken = await retryWithBackoff(
() => exchangeForAppToken(oidcToken, permissions),
{
shouldRetry: (error) => !(error instanceof WorkflowValidationSkipError),
},
);
console.log("App token successfully obtained");
+7
View File
@@ -3,6 +3,7 @@ export type RetryOptions = {
initialDelayMs?: number;
maxDelayMs?: number;
backoffFactor?: number;
shouldRetry?: (error: Error) => boolean;
};
export async function retryWithBackoff<T>(
@@ -14,6 +15,7 @@ export async function retryWithBackoff<T>(
initialDelayMs = 5000,
maxDelayMs = 20000,
backoffFactor = 2,
shouldRetry,
} = options;
let delayMs = initialDelayMs;
@@ -27,6 +29,11 @@ export async function retryWithBackoff<T>(
lastError = error instanceof Error ? error : new Error(String(error));
console.error(`Attempt ${attempt} failed:`, lastError.message);
if (shouldRetry && !shouldRetry(lastError)) {
console.error("Error is not retryable, giving up immediately");
throw lastError;
}
if (attempt < maxAttempts) {
console.log(`Retrying in ${delayMs / 1000} seconds...`);
await new Promise((resolve) => setTimeout(resolve, delayMs));