---
title: abort-signal-timeout-in-workflow
description: AbortSignal.timeout() cannot be used inside workflow functions because it relies on real timers which break deterministic replay.
type: troubleshooting
summary: Use sleep() with AbortController instead of AbortSignal.timeout() in workflow functions.
prerequisites:
  - /docs/foundations/workflows-and-steps
related:
  - /docs/foundations/cancellation
  - /docs/api-reference/workflow/sleep
  - /docs/errors/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](/docs/foundations/workflows-and-steps) to support replay — they run the same code multiple times during the workflow's lifecycle, using the [event log](/docs/how-it-works/event-sourcing) 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()`](/docs/api-reference/workflow/sleep) with an `AbortController` to create a deterministic timeout that cancels in-flight work:

**Before (incorrect):**

{/* @skip-typecheck: intentionally incorrect example */}
```typescript lineNumbers
export async function workflow() {
  "use workflow";

  // This will throw an error
  const signal = AbortSignal.timeout(10_000); // [!code highlight]
  const result = await fetchData(signal);
  return result;
}
```

**After (correct):**

```typescript lineNumbers
import { sleep } from "workflow";

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

  const controller = new AbortController(); // [!code highlight]
  void sleep("10s").then(() => controller.abort()); // [!code highlight]

  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()` + `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; if not, the timer fires `controller.abort()`, `fetch` rejects with an `AbortError`, and the step's failure propagates to the workflow as a `FatalError` (no retries — abort is intentional cancellation).

<Callout type="info">
`AbortSignal.timeout()` works normally inside step functions, since steps have full Node.js runtime access and are not replayed.
</Callout>

## Related

- [Cancellation](/docs/foundations/cancellation) — Patterns for cancelling in-flight work
- [`sleep()` API Reference](/docs/api-reference/workflow/sleep) — Durable sleep primitive
- [Workflows and Steps](/docs/foundations/workflows-and-steps) — Why workflow functions must be deterministic
- [`setTimeout` in Workflow](/docs/errors/timeout-in-workflow) — Similar restriction on `setTimeout`
