Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/retry-transient-errors-js.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merge python changeset into it

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'e2b': minor
---

feat(js-sdk): automatically retry requests on transient failures. The SDK now retries on connection errors and `429`/`502`/`503`/`504` responses using exponential backoff with jitter, and honors a server-provided `Retry-After` header (so rate limiting is handled transparently). Idempotent requests are retried on any transient failure; non-idempotent requests (e.g. `Sandbox.create`) are only retried when the server provably did not process them (e.g. throttling). Configure via the new `retries` option (or `E2B_MAX_RETRIES` env var); set `retries: 0` to disable. Defaults to `3` retries.
5 changes: 5 additions & 0 deletions .changeset/retry-transient-errors-python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@e2b/python-sdk': minor
---

feat(python-sdk): automatically retry requests on transient failures. The SDK now retries on connection errors and `429`/`502`/`503`/`504` responses using exponential backoff with jitter, and honors a server-provided `Retry-After` header (so rate limiting is handled transparently). Idempotent requests are retried on any transient failure; non-idempotent requests (e.g. `Sandbox.create`) are only retried when the server provably did not process them (e.g. throttling). The envd RPC retry now also uses backoff between attempts. Configure via the new `retries` option (or `E2B_MAX_RETRIES` env var); set `retries=0` to disable. Defaults to `3` retries.
3 changes: 2 additions & 1 deletion packages/js-sdk/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createApiFetch } from './http2'
import { ConnectionConfig } from '../connectionConfig'
import { AuthenticationError, RateLimitError, SandboxError } from '../errors'
import { createApiLogger } from '../logs'
import { withRetry } from '../retry'

const API_KEY_PATTERN = /^e2b_[0-9a-f]+$/
const API_KEY_EXAMPLE = `e2b_${'0'.repeat(40)}`
Expand Down Expand Up @@ -95,7 +96,7 @@ class ApiClient {

this.api = createClient<paths>({
baseUrl: config.apiUrl,
fetch: createApiFetch(),
fetch: withRetry(createApiFetch(), config.retries),
// In HTTP 1.1, all connections are considered persistent unless declared otherwise
// keepalive: true,
headers: {
Expand Down
16 changes: 16 additions & 0 deletions packages/js-sdk/src/connectionConfig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Logger } from './logs'
import { getEnvVar, version } from './api/metadata'
import { runtime } from './utils'
import { resolveMaxRetries } from './retry'

// Remove once all deployments support sandbox subdomains
const supportedDomains = ['e2b.app', 'e2b.dev', 'e2b.pro', 'e2b-staging.dev']
Expand Down Expand Up @@ -57,6 +58,19 @@ export interface ConnectionOpts {
* @default 60_000 // 60 seconds
*/
requestTimeoutMs?: number
/**
* Number of times to retry a request after a transient failure (e.g. a
* network error, a `429` rate-limit, or a `502`/`503`/`504`). Retries use
* exponential backoff with jitter and honor a server-provided `Retry-After`
* header. Non-idempotent requests (e.g. creating a sandbox) are only retried
* when the server provably did not process the request (e.g. throttling, a
* refused connection, or a DNS failure), avoiding duplicate side effects.
*
* Set to `0` to disable retries.
*
* @default E2B_MAX_RETRIES // environment variable or `3`
*/
retries?: number
Comment thread
jakubno marked this conversation as resolved.
/**
* Logger to use for logging messages. It can accept any object that implements `Logger` interface—for example, {@link console}.
*/
Expand Down Expand Up @@ -180,6 +194,7 @@ export class ConnectionConfig {
readonly logger?: Logger

readonly requestTimeoutMs: number
readonly retries: number

readonly apiKey?: string
readonly accessToken?: string
Expand All @@ -192,6 +207,7 @@ export class ConnectionConfig {
this.domain = opts?.domain || ConnectionConfig.domain
this.accessToken = opts?.accessToken || ConnectionConfig.accessToken
this.requestTimeoutMs = opts?.requestTimeoutMs ?? REQUEST_TIMEOUT_MS
this.retries = resolveMaxRetries(opts?.retries)
this.logger = opts?.logger
this.headers = opts?.headers || {}
this.headers['User-Agent'] = `e2b-js-sdk/${version}`
Expand Down
Loading
Loading