From 7daa9908c21bb374eb4c101031a5247adebec939 Mon Sep 17 00:00:00 2001 From: ANSHSINGH050404 <114014895+ANSHSINGH050404@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:24:34 +0530 Subject: [PATCH] fix(core): enforce maxElapsed against wall-clock in ExponentialBackoff.execute Closes #3726 --- .changeset/backoff-maxelapsed-wallclock.md | 5 +++++ packages/core/src/v3/apps/backoff.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .changeset/backoff-maxelapsed-wallclock.md diff --git a/.changeset/backoff-maxelapsed-wallclock.md b/.changeset/backoff-maxelapsed-wallclock.md new file mode 100644 index 00000000000..f4f0c5fb0fe --- /dev/null +++ b/.changeset/backoff-maxelapsed-wallclock.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Honor `maxElapsed` against real wall-clock time in `ExponentialBackoff.execute()`, including time spent inside the callback—not only sleep delays. diff --git a/packages/core/src/v3/apps/backoff.ts b/packages/core/src/v3/apps/backoff.ts index cdd467e3f82..53e275ba07d 100644 --- a/packages/core/src/v3/apps/backoff.ts +++ b/packages/core/src/v3/apps/backoff.ts @@ -290,8 +290,16 @@ export class ExponentialBackoff { > { let elapsedMs = 0; let finalError: unknown = undefined; + // maxElapsed is in seconds (see class docs). Enforce wall-clock time for + // the whole execute() loop (sleeps + callbacks), not only sleep delays. + const maxElapsedMs = this.#maxElapsed * 1000; + const wallStart = Date.now(); for await (const { delay, retry } of this) { + if (Date.now() - wallStart > maxElapsedMs) { + break; + } + const start = Date.now(); if (retry > 0) { @@ -299,6 +307,10 @@ export class ExponentialBackoff { await timeout(delay.milliseconds); } + if (Date.now() - wallStart > maxElapsedMs) { + break; + } + let attemptTimeout: NodeJS.Timeout | undefined = undefined; try {