Permissions, flags and failures Required
Give every request a time limit
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 to do it
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 says. After 3 seconds, show "Still working" in the view. Retry reads with backoff and jitter, at most 3 times. Honour Retry-After. Never repeat a write without an idempotency key. Cancel requests whose answer is no longer needed, such as an old search.
Example
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);
}
}
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)
How to 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.
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.