From c29a10e783a52af5d98cb69a4a43fa19cd6a79b1 Mon Sep 17 00:00:00 2001 From: Dillon Mulroy Date: Fri, 17 Apr 2026 14:39:58 -0400 Subject: [PATCH] types: add WorkersRpcError and DurableObjectError interfaces Workers RPC tunneled exceptions (from service bindings, WorkerEntrypoint, RpcTarget, Durable Object stubs, or Workflows) can carry extra boolean properties that hint at how the caller should handle the failure: - retryable (Type::DISCONNECTED) - overloaded (Type::OVERLOADED) - remote (error originated on the remote side) - durableObjectReset (DO-specific: stub is broken, recreate it) These are stamped onto the JS Error object by addAdditionalInfo() in src/workerd/jsg/util.c++. Until now they were documented at https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ but had no exported TypeScript type, so every consumer (including cloudflare/agents's isErrorRetryable) had to reinvent an inline cast like "err as { retryable?: boolean; overloaded?: boolean }". Expose them as two ambient interfaces in types/defines/rpc.d.ts: * WorkersRpcError -- base, covers retryable/overloaded/remote. Applies to any tunneled Workers RPC exception. * DurableObjectError extends WorkersRpcError -- adds durableObjectReset, which is only set by actor-specific code paths (actor-cache, actor-sqlite, input/output gates, alarm handler, actor constructor, actor eviction). Both are optional-property interfaces matching what the runtime actually writes; neither property is ever set to false. Regenerated types/generated-snapshot/{latest,experimental}/ and added type-level tests in types/test/types/rpc.ts. Fixes #6577 --- types/defines/rpc.d.ts | 51 +++++++++++++++++++ .../experimental/index.d.ts | 47 +++++++++++++++++ .../generated-snapshot/experimental/index.ts | 47 +++++++++++++++++ types/generated-snapshot/latest/index.d.ts | 47 +++++++++++++++++ types/generated-snapshot/latest/index.ts | 47 +++++++++++++++++ types/test/types/rpc.ts | 38 ++++++++++++++ 6 files changed, 277 insertions(+) diff --git a/types/defines/rpc.d.ts b/types/defines/rpc.d.ts index 1b43f0ff4b9..a9f868bebdb 100644 --- a/types/defines/rpc.d.ts +++ b/types/defines/rpc.d.ts @@ -159,6 +159,57 @@ declare namespace Rpc { > } +/** + * Error thrown across a Workers RPC boundary — for example, from a + * `DurableObjectStub`, a service binding, a `WorkerEntrypoint`, or an + * `RpcTarget`. The Workers runtime annotates tunneled exceptions with extra + * boolean hints so client code can decide whether to retry, back off, or give + * up. + * + * All properties are optional and only present when the runtime has set them; + * they are never set to `false`. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface WorkersRpcError extends Error { + /** + * `true` when the failure is transient (for example, a lost network + * connection) and the operation is safe to retry, typically with randomized + * exponential backoff. Only retry if the request is idempotent. + */ + retryable?: boolean; + + /** + * `true` when the RPC target is overloaded. Do NOT retry: additional + * requests will worsen the overload and increase the overall error rate. + */ + overloaded?: boolean; + + /** + * `true` when the exception originated on the remote side, either from user + * code running inside the RPC target or from infrastructure running on its + * behalf (for example, when a Durable Object exceeds its memory or CPU + * limits). + */ + remote?: boolean; +} + +/** + * Error thrown by a `DurableObjectStub` when a call to a remote Durable Object + * fails. Extends {@link WorkersRpcError} with Durable-Object-specific hints. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface DurableObjectError extends WorkersRpcError { + /** + * `true` when the Durable Object was reset (for example, due to a + * redeployment, an internal restart, or a storage operation that exceeded + * its timeout). The current stub is broken; create a new `DurableObjectStub` + * before issuing further requests. + */ + durableObjectReset?: boolean; +} + declare namespace Cloudflare { // Type of `env`. // diff --git a/types/generated-snapshot/experimental/index.d.ts b/types/generated-snapshot/experimental/index.d.ts index 8c14269bbcf..2df9f30df50 100755 --- a/types/generated-snapshot/experimental/index.d.ts +++ b/types/generated-snapshot/experimental/index.d.ts @@ -14196,6 +14196,53 @@ declare namespace Rpc { Exclude> >; } +/** + * Error thrown across a Workers RPC boundary — for example, from a + * `DurableObjectStub`, a service binding, a `WorkerEntrypoint`, or an + * `RpcTarget`. The Workers runtime annotates tunneled exceptions with extra + * boolean hints so client code can decide whether to retry, back off, or give + * up. + * + * All properties are optional and only present when the runtime has set them; + * they are never set to `false`. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface WorkersRpcError extends Error { + /** + * `true` when the failure is transient (for example, a lost network + * connection) and the operation is safe to retry, typically with randomized + * exponential backoff. Only retry if the request is idempotent. + */ + retryable?: boolean; + /** + * `true` when the RPC target is overloaded. Do NOT retry: additional + * requests will worsen the overload and increase the overall error rate. + */ + overloaded?: boolean; + /** + * `true` when the exception originated on the remote side, either from user + * code running inside the RPC target or from infrastructure running on its + * behalf (for example, when a Durable Object exceeds its memory or CPU + * limits). + */ + remote?: boolean; +} +/** + * Error thrown by a `DurableObjectStub` when a call to a remote Durable Object + * fails. Extends {@link WorkersRpcError} with Durable-Object-specific hints. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface DurableObjectError extends WorkersRpcError { + /** + * `true` when the Durable Object was reset (for example, due to a + * redeployment, an internal restart, or a storage operation that exceeded + * its timeout). The current stub is broken; create a new `DurableObjectStub` + * before issuing further requests. + */ + durableObjectReset?: boolean; +} declare namespace Cloudflare { // Type of `env`. // diff --git a/types/generated-snapshot/experimental/index.ts b/types/generated-snapshot/experimental/index.ts index d458fd5dd2f..89c049543be 100755 --- a/types/generated-snapshot/experimental/index.ts +++ b/types/generated-snapshot/experimental/index.ts @@ -14167,6 +14167,53 @@ export declare namespace Rpc { Exclude> >; } +/** + * Error thrown across a Workers RPC boundary — for example, from a + * `DurableObjectStub`, a service binding, a `WorkerEntrypoint`, or an + * `RpcTarget`. The Workers runtime annotates tunneled exceptions with extra + * boolean hints so client code can decide whether to retry, back off, or give + * up. + * + * All properties are optional and only present when the runtime has set them; + * they are never set to `false`. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +export interface WorkersRpcError extends Error { + /** + * `true` when the failure is transient (for example, a lost network + * connection) and the operation is safe to retry, typically with randomized + * exponential backoff. Only retry if the request is idempotent. + */ + retryable?: boolean; + /** + * `true` when the RPC target is overloaded. Do NOT retry: additional + * requests will worsen the overload and increase the overall error rate. + */ + overloaded?: boolean; + /** + * `true` when the exception originated on the remote side, either from user + * code running inside the RPC target or from infrastructure running on its + * behalf (for example, when a Durable Object exceeds its memory or CPU + * limits). + */ + remote?: boolean; +} +/** + * Error thrown by a `DurableObjectStub` when a call to a remote Durable Object + * fails. Extends {@link WorkersRpcError} with Durable-Object-specific hints. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +export interface DurableObjectError extends WorkersRpcError { + /** + * `true` when the Durable Object was reset (for example, due to a + * redeployment, an internal restart, or a storage operation that exceeded + * its timeout). The current stub is broken; create a new `DurableObjectStub` + * before issuing further requests. + */ + durableObjectReset?: boolean; +} export declare namespace Cloudflare { // Type of `env`. // diff --git a/types/generated-snapshot/latest/index.d.ts b/types/generated-snapshot/latest/index.d.ts index 29b0f1a2314..df0b2a9e053 100755 --- a/types/generated-snapshot/latest/index.d.ts +++ b/types/generated-snapshot/latest/index.d.ts @@ -13536,6 +13536,53 @@ declare namespace Rpc { Exclude> >; } +/** + * Error thrown across a Workers RPC boundary — for example, from a + * `DurableObjectStub`, a service binding, a `WorkerEntrypoint`, or an + * `RpcTarget`. The Workers runtime annotates tunneled exceptions with extra + * boolean hints so client code can decide whether to retry, back off, or give + * up. + * + * All properties are optional and only present when the runtime has set them; + * they are never set to `false`. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface WorkersRpcError extends Error { + /** + * `true` when the failure is transient (for example, a lost network + * connection) and the operation is safe to retry, typically with randomized + * exponential backoff. Only retry if the request is idempotent. + */ + retryable?: boolean; + /** + * `true` when the RPC target is overloaded. Do NOT retry: additional + * requests will worsen the overload and increase the overall error rate. + */ + overloaded?: boolean; + /** + * `true` when the exception originated on the remote side, either from user + * code running inside the RPC target or from infrastructure running on its + * behalf (for example, when a Durable Object exceeds its memory or CPU + * limits). + */ + remote?: boolean; +} +/** + * Error thrown by a `DurableObjectStub` when a call to a remote Durable Object + * fails. Extends {@link WorkersRpcError} with Durable-Object-specific hints. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +interface DurableObjectError extends WorkersRpcError { + /** + * `true` when the Durable Object was reset (for example, due to a + * redeployment, an internal restart, or a storage operation that exceeded + * its timeout). The current stub is broken; create a new `DurableObjectStub` + * before issuing further requests. + */ + durableObjectReset?: boolean; +} declare namespace Cloudflare { // Type of `env`. // diff --git a/types/generated-snapshot/latest/index.ts b/types/generated-snapshot/latest/index.ts index 863901436d5..826e089264a 100755 --- a/types/generated-snapshot/latest/index.ts +++ b/types/generated-snapshot/latest/index.ts @@ -13507,6 +13507,53 @@ export declare namespace Rpc { Exclude> >; } +/** + * Error thrown across a Workers RPC boundary — for example, from a + * `DurableObjectStub`, a service binding, a `WorkerEntrypoint`, or an + * `RpcTarget`. The Workers runtime annotates tunneled exceptions with extra + * boolean hints so client code can decide whether to retry, back off, or give + * up. + * + * All properties are optional and only present when the runtime has set them; + * they are never set to `false`. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +export interface WorkersRpcError extends Error { + /** + * `true` when the failure is transient (for example, a lost network + * connection) and the operation is safe to retry, typically with randomized + * exponential backoff. Only retry if the request is idempotent. + */ + retryable?: boolean; + /** + * `true` when the RPC target is overloaded. Do NOT retry: additional + * requests will worsen the overload and increase the overall error rate. + */ + overloaded?: boolean; + /** + * `true` when the exception originated on the remote side, either from user + * code running inside the RPC target or from infrastructure running on its + * behalf (for example, when a Durable Object exceeds its memory or CPU + * limits). + */ + remote?: boolean; +} +/** + * Error thrown by a `DurableObjectStub` when a call to a remote Durable Object + * fails. Extends {@link WorkersRpcError} with Durable-Object-specific hints. + * + * @see https://developers.cloudflare.com/durable-objects/best-practices/error-handling/ + */ +export interface DurableObjectError extends WorkersRpcError { + /** + * `true` when the Durable Object was reset (for example, due to a + * redeployment, an internal restart, or a storage operation that exceeded + * its timeout). The current stub is broken; create a new `DurableObjectStub` + * before issuing further requests. + */ + durableObjectReset?: boolean; +} export declare namespace Cloudflare { // Type of `env`. // diff --git a/types/test/types/rpc.ts b/types/test/types/rpc.ts index 07a4d0346da..cd790061462 100644 --- a/types/test/types/rpc.ts +++ b/types/test/types/rpc.ts @@ -784,6 +784,44 @@ export default >{ >; } + // Verify the shape of WorkersRpcError and DurableObjectError matches what + // the runtime stamps onto tunneled exceptions from RPC / DO stubs. + { + try { + const stub = env.RPC_OBJECT.get(env.RPC_OBJECT.newUniqueId()); + await stub.fetch('https://example.com'); + } catch (e) { + // Base RPC error: retryable / overloaded / remote apply to any + // tunneled Workers RPC exception (Fetcher, service binding, + // WorkerEntrypoint, RpcTarget, Durable Object, Workflow). + const rpcErr = e as WorkersRpcError; + expectTypeOf(rpcErr).toExtend(); + expectTypeOf(rpcErr.retryable).toEqualTypeOf(); + expectTypeOf(rpcErr.overloaded).toEqualTypeOf(); + expectTypeOf(rpcErr.remote).toEqualTypeOf(); + + // `durableObjectReset` is DO-specific and not on the base RPC error. + expectTypeOf().not.toHaveProperty( + 'durableObjectReset' + ); + + // Durable-Object-specific error adds `durableObjectReset`. + const doErr = e as DurableObjectError; + expectTypeOf(doErr).toExtend(); + expectTypeOf(doErr).toExtend(); + expectTypeOf(doErr.retryable).toEqualTypeOf(); + expectTypeOf(doErr.overloaded).toEqualTypeOf(); + expectTypeOf(doErr.remote).toEqualTypeOf(); + expectTypeOf(doErr.durableObjectReset).toEqualTypeOf< + boolean | undefined + >(); + + // Both are assignable to Error (they extend it). + const asError: Error = doErr; + expectTypeOf(asError).toExtend(); + } + } + return new Response(); }, };