# Fix setTimeout clamped to 30 seconds, timeout-based retry broken

## TL;DR

Do not use `setTimeout` for waits longer than 30 seconds in Workers - the runtime clamps the timer and your retry fires early. Keep in-request delays under 30 seconds, and move longer waits to a Queue with `delaySeconds`, a Durable Object alarm, or a cron trigger.

## Verbatim error

```text
workerd: setTimeout clamped to 30 seconds, timeout-based retry broken
```

## Steps

1. Confirm the clamp: log the requested delay and the actual elapsed time around the timer. Expected: delays over 30 seconds fire at roughly 30 seconds.
2. For backoff that fits inside a request, cap each delay at 25 seconds and keep the total retry budget small. Expected: retries fire on the schedule you set.
3. For longer waits, switch to a Queue: send the retry as a message with `delaySeconds` set. Expected: the message redelivers after the requested delay.
4. Alternative for one-shot future wakeups: use a Durable Object `alarm()`. Expected: the alarm fires at the target time.
5. Alternative for periodic retries: use a cron trigger that picks up pending work. Expected: the trigger runs on schedule.
6. Verify by tailing logs around the expected fire time. Expected: the retry runs at the intended delay, not at 30 seconds.

## Use this when

- `setTimeout(60000)` fires at 30 seconds in a Worker
- Exponential backoff stalls or collapses to a fixed 30-second rhythm
- Logs mention timer clamping in workerd

## Not for this skill when

- All your timeouts are under 30 seconds (they work fine)
- You need a request-scoped deadline inside the CPU limit (different pattern)
- The problem is `setInterval` drift rather than the clamp (same fix family, but check the interval logic)

## Variant phrasings

- workerd timer max 30 seconds
- setTimeout not working long delay cloudflare workers
- retry backoff broken workers
- timeout clamped workers runtime

## Why it happens

Workers are event-driven isolates without long-lived processes, so the runtime caps timers at 30 seconds to bound isolate lifetime. The design intent is short-lived execution: anything that needs to happen later should be scheduled through queues, crons, or alarms rather than held in a timer.

## Edge cases

- `setInterval` has the same 30-second clamp.
- Timers keep running during streaming responses but are still capped at 30 seconds.
- There is no `unref()` in workerd; do not port Node timer patterns blindly.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_5oU6qXmn4NzeEJ5-xhTwvQ
