abort-signal-timeout-in-workflow

Error

AbortSignal.timeout() is not supported in workflow functions.
Use sleep() with an AbortController instead.

Why this happens

AbortSignal.timeout() creates a signal that aborts after a real-time delay using an internal timer. Workflow functions must be deterministic to support replay: they run the same code multiple times during the workflow's lifecycle, using the event log to resume execution to the correct point.

Real-time timers break this determinism because:

  • On the first execution, the timer might fire after 10 seconds.
  • On replay, the timer would fire again, but the event log may have already advanced past that point.
  • The timer's behavior depends on wall-clock time, which varies between executions.

How to fix

Use sleep() with an AbortController to create a deterministic timeout that cancels in-flight work:

Before (incorrect):

export async function workflow() {
  "use workflow";

  // This will throw an error
  const signal = AbortSignal.timeout(10_000);
  const result = await fetchData(signal);
  return result;
}

After (correct):

import { sleep } from "workflow";

export async function workflow() {
  "use workflow";

  const controller = new AbortController();
  void sleep("10s").then(() => controller.abort());

  return await fetchData(controller.signal);
}

async function fetchData(signal: AbortSignal) {
  "use step";
  const response = await fetch("https://api.example.com/data", { signal });
  return response.json();
}

The sleep() and AbortController pattern is the durable equivalent of AbortSignal.timeout(). The sleep is recorded in the event log, so it replays deterministically. If fetchData finishes within 10 seconds, you get the response. Otherwise, the timer calls controller.abort(), fetch rejects with an AbortError, and the step's failure propagates to the workflow as a FatalError (no retries because the abort is intentional cancellation).

AbortSignal.timeout() works normally inside step functions, since steps have full Node.js runtime access and are not replayed.