-
Notifications
You must be signed in to change notification settings - Fork 9
Description
Problem
In packages/durabletask-js/src/worker/orchestration-executor.ts, the tryHandleRetry method uses a negative check (retryResult !== false) to determine whether a custom retry handler wants to retry a failed task. This means any value that is not strictly false — including undefined, null, NaN, and Infinity — is treated as "retry".
This causes two distinct failure modes:
-
Infinite retry loop: If a retry handler has a code path that returns
undefined(common JavaScript mistake — a missingreturnstatement in a branch), the task is rescheduled immediately and retries forever, without any delay. -
Invalid timer creation: If the handler returns
NaNorInfinity, these pass thetypeof === "number"check but create timers with invalid fire-at dates, causing unpredictable behavior.
Affected code (line ~795):
if (retryResult !== false) { // BUG: undefined, null, NaN, Infinity all pass this checkExample — handler with missing return:
const retryHandler = async (ctx) => {
if (ctx.lastAttemptNumber >= 5) {
return false;
}
// Oops — forgot "return true" here, returns undefined
};This handler intends to stop retrying after 5 attempts, but on attempts 1–4 it returns undefined, which the SDK treats as "retry immediately" — creating an infinite loop until the 5th attempt.
While TypeScript types (RetryHandlerResult = boolean | number) catch this at compile time, JavaScript consumers and as any casts bypass this protection. The SDK should defensively handle unexpected return values at runtime.
Root Cause
The condition retryResult !== false is a negative check that accepts ANY value except false. The correct pattern is a positive check: only accept values that are explicitly true or a finite number.
Proposed Fix
Change the condition to a positive check:
if (retryResult === true || (typeof retryResult === "number" && Number.isFinite(retryResult)))This ensures:
true→ retry immediately ✓false→ stop retrying ✓- Finite positive number → retry with delay ✓
- Finite zero/negative number → retry immediately ✓ (preserves existing behavior)
undefined,null→ stop retrying ✓ (FIXED)NaN,Infinity→ stop retrying ✓ (FIXED)
Impact
- Severity: High — can cause infinite retry loops, resource exhaustion, and orchestration hangs
- Affected scenarios: Any orchestration using
AsyncRetryHandlerorRetryHandlerwhere the handler has a code path that does not explicitly returntrue,false, or a number - Likelihood: Medium — most common in JavaScript consumers or TypeScript code with partial branch coverage