Service eligibility and regional restrictions
PuppyIP serves only compliant overseas businesses and their authorized personnel. Proxy services are not available in mainland China. The service may only be used for lawful business activities outside mainland China. Use of this service within mainland China is prohibited.
Hosting a proxy IP or server overseas does not change these restrictions. The service must not be provided to end users in mainland China through relaying, forwarding, sharing or resale. Before use, read the Terms of Service.
Key Takeaways
- The runtime retries eligible Durable Object fetch and RPC calls, but it must be able to resend the request unchanged.
- The default is four retries and five attempts total. The ten-second limit applies to when retries may start, not to when the whole call must finish.
- retryable() allows a specific method to run again when delivery of the first attempt is uncertain; mark only methods that are idempotent and safe to repeat.
- Application-level retries multiply runtime attempts. Reduce the outer retry count or set maxRetries: 0 for stubs whose retries your code manages.
Start with the automatic retry boundary
On November 5, 2026, Cloudflare announced automatic retries for Durable Object calls. The runtime now handles fetch() requests and remote method calls (RPC) made through a Durable Object stub, the local handle used to call the object, whether the caller is a Worker or another Durable Object. The baseline automatic retries do not require changes to existing code.
The runtime retries only when the error has retryable set to true and the request either never reached the Durable Object or may have arrived before the connection dropped. In the uncertain-delivery case, the retry uses the same request identity. If the object already received the first attempt, it rejects the retry and the call fails with the original error.
Ten seconds is not a whole-call timeout
By default, the runtime makes up to four retries: the first attempt plus four retries, for five attempts total. Retries use exponential backoff with jitter. No retry starts more than ten seconds after the initial call starts. This limit controls when a retry may begin; it does not require the first attempt to finish within ten seconds.
The runtime never cancels the first attempt. When the ten-second window expires, any retry that is still running is canceled, but the first request may continue. Do not describe ten seconds as a guarantee that the entire call will finish or that the underlying operation has stopped. The call normally returns the first failure; if a later attempt fails with a non-retryable error, it returns that later error instead.
Use retryable() only for methods safe to repeat
When a connection drops, the runtime still sends the retry with the same request identity. If the object received the first request, it rejects the duplicate by default and the call fails with the original error; if the first request never arrived, the retry can proceed. To let an idempotent method run again even when the first request arrived, import retryable() from cloudflare:durable-objects and mark the method on the class prototype.
setQuantity(itemId, quantity) is an example of an idempotent operation: writing the same quantity again leaves the same state. addOne(itemId) increments on every run, so repeating it changes the result and it should not be marked. retryable() is not a general exactly-once guarantee and does not make emails, charges, or other external side effects idempotent. It changes only whether a marked method may run when delivery of the first attempt is uncertain.
Set retry limits per stub
When creating a Durable Object stub, pass a retry object in the namespace get() or getByName() options. maxRetries is the number of retries after the first attempt. It must be an integer from 0 to 10 and defaults to 4. timeoutMs is the number of milliseconds after the call starts when no further retry may begin. It must be an integer from 500 to 60,000 and defaults to 10,000.
These settings apply to fetch() and RPC calls made through that stub; omitted values use their defaults. Using retryable() and the retry option requires Wrangler 4.158.0 or later; the baseline automatic retries do not.
Review existing loops to avoid multiplying attempts
If your application already retries the same Durable Object call, each outer attempt can now contain up to five runtime attempts. For example, if an outer loop makes at most three calls and each uses the default stub settings, the operation could make up to fifteen Durable Object attempts. This is a hypothetical illustration of layered retries, not a claim about observed traffic or test results.
You can reduce the application-level attempt count or set maxRetries: 0 on stubs whose retry policy is fully managed by your application. First decide whether the outer retry is still needed and whether the method can safely run again. Do not mistake the new default for a global maximum across all layers.
Not every request or error can be replayed
Automatic retries apply only when the runtime can resend a call unchanged. A fetch() request with a ReadableStream body is not retried; bodies such as strings, ArrayBuffer, Blob, FormData, and URLSearchParams can be resent. RPC calls whose arguments include a stream, stub, function, or RpcTarget are not retried; arguments made only of structured-cloneable values can be replayed.
Promise pipelining means calling through a result before it resolves. Those calls, and calls through stubs received as RPC arguments or return values, are not retried. Once you pipeline a call on a result, the runtime also stops retrying the call that produces that result. Automatic retries apply only to stubs created through a Durable Object namespace with get() or getByName(). Errors thrown by object code, errors after the request reaches the object, errors with overloaded set to true, and HTTP error responses such as HTTP 500 do not trigger runtime retries. retryable() does not expand these error or request limits.
Sources
Frequently Asked Questions
Do subclasses, overrides, or wrapped methods inherit a retryable() mark?
The mark belongs to a specific function. A subclass inherits a marked method, but an override, bound copy, or wrapper is not automatically marked. Pass the final function that will run to retryable() as well.
What errors do invalid retry options produce?
If maxRetries or timeoutMs is not a number, get() and getByName() throw a TypeError. If the value is numeric but not an integer or outside its allowed range, they throw a RangeError.
Do runtime retries use additional Worker subrequest quota?
No. Runtime retries for a Durable Object call do not count toward the subrequest limit; only the first attempt counts.