---
title: "Give every request a time limit · Agent Wranglers"
url: https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/request-timeouts
updated: 2026-10-20
licence: "All rights reserved"
---

# Give every request a time limit

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Permissions, flags and failures](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures)
- Rule: Give every request from the screen a time limit, say when a request is slow, and retry only reads and requests with an idempotency key.
- How: Abort reads after 10 to 15 seconds and writes after 30, with `AbortSignal.timeout`. Keep these longer than the server's own limits, as [timeouts](https://agentwranglers.se/playbook/api-design/reliability/timeouts) says. After 3 seconds, show "Still working" in the view. Retry reads with [backoff and jitter](https://agentwranglers.se/playbook/api-design/reliability/retry-backoff), at most 3 times. Honour `Retry-After`. Never repeat a write without an [idempotency key](https://agentwranglers.se/playbook/api-design/reliability/idempotency-keys). Cancel requests whose answer is no longer needed, such as an old search.
- Check: A browser test holds the orders request open and never answers it. "Still working" appears after 3 seconds, and the error view with a retry button appears at the time limit. A second test holds a write without an idempotency key open, and it is sent once only.

Example:

```ts
const READ_TIMEOUT_MS = 12_000;
const SLOW_AFTER_MS = 3_000;

export async function loadWithTimeLimit<T>(url: string, onSlow: () => void, cancel?: AbortSignal): Promise<T> {
  const slowTimer = setTimeout(onSlow, SLOW_AFTER_MS);
  const signals = [AbortSignal.timeout(READ_TIMEOUT_MS), ...(cancel ? [cancel] : [])];
  try {
    const response = await fetch(url, { signal: AbortSignal.any(signals) });
    if (!response.ok) throw new LoadError(response.status, await response.json().catch(() => null));
    return response.json();
  } finally {
    clearTimeout(slowTimer);
  }
}
```

```python
from playwright.sync_api import expect

def test_slow_orders_show_progress_then_error(page):
    held_open = []
    page.route("**/api/orders*", held_open.append)
    page.goto("https://staging.example.com/orders")
    expect(page.get_by_text("Still working")).to_be_visible(timeout=4_000)
    expect(page.get_by_role("button", name="Try again")).to_be_visible(timeout=15_000)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers app interface playbook: https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/request-timeouts`.

- Previous: [Contain each crash to its panel](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/error-boundaries)
- Next: [Say when the network is gone](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures/offline-behaviour)
- [Permissions, flags and failures](https://agentwranglers.se/playbook/app-ui/permissions-flags-and-failures)
