NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #1602 most downloaded on PyPI
E2B SDK that give agents cloud environments
Last release 3 days ago
01 Oct 2026
Ships on a steady schedule
a new release about every 2 weeks
Nearly every release is documented
notes for 57 of the last 60 stable releases
180 versions withdrawn
withdrawn after publishing
3 years old
363 releases · first in 2023
One column per quarter.
2de0cc3 : Cap sandbox fork count at 20. e2b sandbox fork --count , JavaScript Sandbox.fork({ count }) , and Python Sandbox.fork(count=...) reject a co
2de0cc3: Cap sandbox fork count at 20. e2b sandbox fork --count, JavaScript Sandbox.fork({ count }), and Python Sandbox.fork(count=...) reject a count outside 1–20 before the API call. Counts from 21 through 100 used to reach the API. Omitting count still leaves the field off the request so the API default applies.
03887ad: Apply .dockerignore and fileIgnorePatterns / file_ignore_patterns the way Docker does when copying files into a template, so ignored files are no longer uploaded or included in the files hash:
node_modules, .git, dist/) now excludes everything under it, and a leading / is ignored.! patterns re-include paths; the last matching pattern wins.. or .. segments (for example copy(".")).fileIgnorePatterns / file_ignore_patterns are applied after the .dockerignore lines, so they take precedence.{a,b}) is not supported, as in Docker. In JS, fileIgnorePatterns such as **/*.{env,pem} previously expanded and now match {env,pem} literally; list each pattern separately instead (**/*.env, **/*.pem).[) now raises an error.Directory sizes are no longer part of the files hash, since they depend on the filesystem rather than on the copied files. The files hash of copy() steps that copy directories or are affected by ignore patterns changes once, so those steps are rebuilt on the next build.
2f92cc3: Add an http_version (Python) / httpVersion (JS) connection option, "1.1" or "2" (default), also settable with the E2B_HTTP_VERSION environment variable, to pin requests to the E2B API, to sandboxes (commands, filesystem, PTY) and to volume content to HTTP/1.1. It can also be bound once on a client: E2B(http_version="1.1") / new E2B({ httpVersion: '1.1' }).
502 and 503 responses and failures to establish the connection (connection refused, DNS, unreachable host), in addition to 429. Any other network error (dropped connection, or the opaque TypeError browsers and Cloudflare Workers raise) is retried too, except for POST operations not known to be safe to replay (sandbox creation, fork, snapshot, volume/secret/API-key/webhook creation), which the server may already have processed. Retry-After is honored when the server sends one; otherwise retries back off exponentially with jitter, starting at 100 ms and capped at 10 s. The existing retries option (default 3, 0 to disable) and request-timeout budget apply to all of them.Commands.run and Commands.connect2de0cc3: Cap sandbox fork count at 20. e2b sandbox fork --count, JavaScript Sandbox.fork({ count }), and Python Sandbox.fork(count=...) reject a count outside 1–20 before the API call. Counts from 21 through 100 used to reach the API. Omitting count still leaves the field off the request so the API default applies.
03887ad: Apply .dockerignore and fileIgnorePatterns / file_ignore_patterns the way Docker does when copying files into a template, so ignored files are no longer uploaded or included in the files hash:
node_modules, .git, dist/) now excludes everything under it, and a leading / is ignored.! patterns re-include paths; the last matching pattern wins.. or .. segments (for example copy(".")).fileIgnorePatterns / file_ignore_patterns are applied after the .dockerignore lines, so they take precedence.{a,b}) is not supported, as in Docker. In JS, fileIgnorePatterns such as **/*.{env,pem} previously expanded and now match {env,pem} literally; list each pattern separately instead (**/*.env, **/*.pem).[) now raises an error.Directory sizes are no longer part of the files hash, since they depend on the filesystem rather than on the copied files. The files hash of copy() steps that copy directories or are affected by ignore patterns changes once, so those steps are rebuilt on the next build.
2f92cc3: Add an http_version (Python) / httpVersion (JS) connection option, "1.1" or "2" (default), also settable with the E2B_HTTP_VERSION environment variable, to pin requests to the E2B API, to sandboxes (commands, filesystem, PTY) and to volume content to HTTP/1.1. It can also be bound once on a client: E2B(http_version="1.1") / new E2B({ httpVersion: '1.1' }).
502 and 503 responses, in addition to 429, and after a network error once the request was written (dropped connection) — except for POST operations not known to be safe to replay (sandbox creation, fork, snapshot, volume/secret/API-key/webhook creation), which the server may already have processed. Failures to establish the connection keep being retried for every operation (E2B_CONNECTION_RETRIES). Retry-After is honored when the server sends one; otherwise retries back off exponentially with jitter, starting at 0.1 seconds and capped at 10 seconds. The existing retries option (default 3, 0 to disable) and request-timeout budget apply to all of them.commands/files traffic is now balanced across HTTP/2 connections the way the JS SDK's undici agent does it, instead of being sharded into a fixed number of pools by sandbox ID. Every request goes to the connection with the fewest streams in flight to its host; a new connection is dialed only when all existing ones already carry E2B_STREAMS_PER_CONNECTION streams to that host (default 100), up to E2B_MAX_CONNECTIONS (default 200). Envd RPC and HTTP calls share the same connections. E2B_ENVD_POOL_SHARDS and the pool_shard argument of the internal transport factories (get_transport, get_httpx_transport, get_pyqwest_transport, get_envd_transport) are removed.Commands.run and Commands.connectThe secure option on Sandbox.create is deprecated: every sandbox is secured, so the option is still accepted but ignored.
5c0f6eb: Remove SDK-side defaults from API request payloads so the API defaults apply when options are omitted. Sandbox create/fork/connect no longer preset a 5-minute timeout, fork no longer presets count: 1, create no longer presets allow_internet_access, pause no longer presets keeping memory, and template builds no longer preset CPU/memory. Explicitly provided values are still sent unchanged.
Sandbox create and connect now use the v2 API endpoints (POST /v2/sandboxes, POST /v2/sandboxes/{id}/connect), which default timeout to 5 minutes and always secure envd access. The secure option on Sandbox.create is deprecated: every sandbox is secured, so the option is still accepted but ignored.
count argument. The API validates the requested fork count and rejects invalid values.e1fbe86 : Bump undici to 7.29.1 and the optional undici8 fallback to 8.10.2 to pick up the September 2026 security fixes.
5b015ad: Removed the V1 template build operations and schemas from the generated API clients. The API no longer serves them (runtime 87968fc1e1fa); control planes carrying that change answer 410 Gone. Template builds go through the Template SDK.
Visible removal, classified minor: the JS paths namespace loses POST /templates, POST /templates/{templateID}, POST /templates/{templateID}/builds/{buildID} and POST /v2/templates, and components['schemas'] loses TemplateLegacy, TemplateBuildRequest and TemplateBuildRequestV2; the Python e2b.api.client.models package loses TemplateLegacy, TemplateBuildRequest and TemplateBuildRequestV2, and e2b.api.client.api.templates loses the post_templates, post_templates_template_id, post_templates_template_id_builds_build_id and post_v2_templates modules. No SDK method accepted or returned them; code that imported these names directly must drop the import.
The regenerated clients also pick up a documented 429 on most operations, a 409 on template create (v3), the upload-request headers on the build file-upload link, minLength: 1 on the v2 build source fields, and a deprecation marker on the always-empty logs field of the build status.
x-ms-blob-type on the upload request, which its signed URL cannot carry, so COPY instructions failed on Azure-backed clusters. GCS- and S3-backed clusters return no headers and are unaffected.@connectrpc/connect and @connectrpc/connect-web to 2.2.0 (fixes Http2SessionManager.verify() hanging when the connection closes mid-verification and stops delivering stream messages after cancellation).ReadableStreams in the browser silently sending the text [object ReadableStream] instead of the data. Buffering a stream drained it with new Response(stream), which accepts any async iterable on Node (an undici extension) but only its own stream class in a browser, stringifying anything else. Streams are now drained through the reader, which every implementation supports.TimeoutError in the browser. When the connection to a sandbox drops mid-request the SDK probes the sandbox's health to tell a killed sandbox apart from a transient network blip, but the probe only ran for connection-dropped wordings it recognized, and the browser's (network error) was missing — so killing a sandbox while a command was running surfaced a generic SandboxError: [unknown] network error instead of TimeoutError: ... The sandbox was killed or reached its end of life while the request was in flight.undici to 7.29.1 and the optional undici8 fallback to 8.10.2 to pick up the September 2026 security fixes.90e3fc5 : Reject invalid Sandbox.create lifecycle options and Sandbox.connect onResume values before requiring an API key, matching the Python SDK.
Sandbox.create lifecycle options and Sandbox.connect onResume values before requiring an API key, matching the Python SDK.onResume / on_resume needs a control plane that knows the option: an older self-hosted or BYOC control plane drops the memory field and restores memory while reporting success, instead of rejecting the request.429 responses using the server's delta-seconds Retry-After delay. Retries can be configured or disabled with retries, and stop when waiting would exhaust the request timeout. Envd requests, including filesystem operations, and volume-content requests are not retried.f842aa8 : Expose a configurable minimum free-disk target with minFreeDiskMb in JavaScript, min_free_disk_mb in Python, and --min-free-disk-mb in templ
minFreeDiskMb in JavaScript, min_free_disk_mb in Python, and --min-free-disk-mb in template create. Omission uses the team default, while explicit zero requests no minimum growth. Growth is best effort and never shrinks an existing filesystem.08efa36 : Add httpsPorts (JS) / https_ports (Python) to the sandbox network config. Ports listed there have their public URLs proxied to the sandbox o
httpsPorts (JS) / https_ports (Python) to the sandbox network config. Ports listed there have their public URLs proxied to the sandbox over HTTPS — use it when the service listening on the port serves TLS itself. This is not TLS passthrough: traffic is still terminated at the E2B proxy and re-encrypted on the hop to the sandbox, and the backend certificate is not verified, so self-signed certificates work. The configured ports are also returned in the sandbox info network config.ServiceBusyError (JavaScript) / ServiceBusyException (Python) for HTTP 503 responses: the API refused the operation because the service or the node running the sandbox is temporarily busy, the sandbox is unchanged, and the call can be retried. A refused pause() is the first case. The base SandboxError / SandboxException also carries the HTTP status as statusCode / status_code when the error came from an API response, so callers can branch on the status without parsing the message. Like AuthenticationError / AuthenticationException, the new class does not subclass the sandbox base error: it is raised for every 503 whatever the operation, so catch it explicitly.onTimeout / on_timeout and onResume / on_resume now raise InvalidArgumentError / InvalidArgumentException for a value outside their two literals, instead of silently resolving it to the other one. Both are resolved into a boolean before the request is built, so the value never reaches the API and a typo cannot be rejected server-side: on_timeout="Pause" previously resolved to kill and deleted the sandbox and its snapshot at timeout, and on_resume="Reboot" previously restored the memory the caller asked to skip. A nullish value still means "not configured" and leaves the choice to the API.6146cd0 : Improve reliability for high-concurrency sandbox workloads by spreading envd traffic across four HTTP/2 connection pools. Set E2B_ENVD_POOL_
E2B_ENVD_POOL_SHARDS before importing the SDK to adjust the pool count.The validateApiKey / validate_api_key option is deprecated and has no effect, and the E2B_VALIDATE_API_KEY environment variable is no longer read. The…
e2b_ hex format — only that a key is present. The validateApiKey/validate_api_key option is deprecated and has no effect, and the E2B_VALIDATE_API_KEY environment variable is no longer read. The server remains the source of truth for key validity.67c06e0: Point the two Code Interpreter README links at code-interpreting/analyze-data-with-ai instead of the code-interpreting section index. The index has no landing page and 307s to that article, dropping the query string on the way, so the UTM parameters were lost before the reader arrived. Linking at the resolved path keeps them.
8943d6f: Update runtime dependencies: tar 7.5.22 and @bufbuild/protobuf 2.14.0 in the JS SDK, statuses 2.0.2, async-listen 3.1.0 and yup 1.7.1 in the CLI. No behavior change.
182b498: Point the README documentation links at docs.e2b.dev instead of e2b.dev/docs. The docs site moved to its own subdomain and has no /docs path prefix there, so e2b.dev/docs serves a 308 to docs.e2b.dev/ and e2b.dev/docs/code-interpreting maps to docs.e2b.dev/code-interpreting. The UTM parameters are unchanged and survived the redirect, so this removes a redirect hop rather than fixing broken attribution.
b802997: Fix two network.egressProxy / network["egress_proxy"] cases an untyped caller reaches.
The JS SDK had no shape guard: buildEgressProxyBody rebuilds the body from the known fields, so an address that was missing or not a string vanished and the caller got an API error about a config they never wrote ({"egressProxy":{}}). It now raises InvalidArgumentError naming the option, the way the Python SDK already did:
// InvalidArgumentError: network egressProxy must be an object with a string
// 'address' (e.g. 'proxy.example.com:1080').
await Sandbox.create({
network: { egressProxy: 'proxy.example.com:1080' as never },
})A null / None username or password is now treated as absent instead of being serialized as a JSON null the API rejects — reading a credential out of an unset environment variable is how a caller lands there, and it means the proxy takes no credentials:
await Sandbox.create({
network: {
egressProxy: {
address: 'proxy.example.com:1080',
// Unset in the environment; the proxy takes no credentials.
username: process.env.PROXY_USER,
},
},
})Sandbox.create(
network={
"egress_proxy": {
"address": "proxy.example.com:1080",
"username": os.environ.get("PROXY_USER"),
},
},
)b17b726 : Stop the shared retrying transports from mirroring streamed request bodies in memory. pyqwest's retry middleware keeps a non- bytes body rep
bytes body replayable byfiles.write of a file-like object orvolume.write_file grew a full in-RAM mirror and peak memory scaled with fileRetryMode.UNBUFFERED: a streamedConnectionError is raised onlybytes8787dfe : Add sorting and new filters to Sandbox.list . The order option ( 'asc' / 'desc' , default 'desc' ) sorts sandboxes by start time across the
Sandbox.list. The order option ('asc' / 'desc', default 'desc') sorts sandboxes by start time across the whole paginated dataset, and the query now supports startedAfter / started_after (inclusive lower bound on start time) and template (exact template ID or alias) filters, all applied server-side before pagination. The CLI e2b sandbox list command exposes these via --order, --started-after, and --template.5759f17 : Add an E2B client that binds a connection config once and exposes the resource surfaces off it, so a single process can talk to several API
5759f17: Add an E2B client that binds a connection config once and exposes the resource surfaces off it, so a single process can talk to several API keys, domains or deployments. The classes it exposes are per-client subclasses of the real Sandbox/Volume/Template/Secret classes, so they behave exactly like the top-level ones — per-call options still win over the client's options, which win over the environment variables. The named top-level exports are unchanged and keep reading the environment.
Nothing existing changes: Template is now the TemplateBase class made callable as a factory, so Template(...), the statics and instanceof keep working, and the default export is still Sandbox.
import { E2B } from 'e2b'
const { Sandbox, Volume, Template, Secret } = new E2B({
apiKey: 'e2b_***',
domain: 'e2b.dev',
})
const sandbox = await Sandbox.create()
const volume = await Volume.create('my-volume')
const exists = await Template.exists('my-template')
await Template.build(Template().fromPythonImage('3'), 'my-env')
await Secret.create('openai-api-key', 'sk-***')from e2b import E2B
client = E2B(api_key="e2b_***", domain="e2b.dev")
Sandbox, Volume, Template = client.Sandbox, client.Volume, client.Template
Secret = client.Secret
sandbox = Sandbox.create()
volume = Volume.create("my-volume")
exists = Template.exists("my-template")
secret = Secret.create("openai-api-key", "sk-***")
# Async variants are exposed too.
AsyncSandbox = client.AsyncSandbox
async_sandbox = await AsyncSandbox.create()f89f8c3 : Add Secrets Management to the SDK. The Secret class (and AsyncSecret in Python) now manages E2B secrets: create and update store secret valu
Secret class (and AsyncSecret in Python) now manages E2B secrets: create and update store secret values (write-only — no read surface returns them), getInfo / get_info and the paginated list read metadata, exists and destroy are idempotent existence and lifecycle helpers, and fill formats the ${e2b.secrets.name} placeholder that the runtime resolves to the secret's current value.61503f7: Report iam.tokens.toJSON, .then, .toString and .valueOf as unregistered workload tokens in a network transform callback. These four names were exempt from the unknown-token guard because the runtime reads them off any object it serializes, awaits or coerces, so referencing one as a token name used to serialize Bearer undefined or a built-in's source text into the rule. Such a value carries no placeholder for the egress proxy to resolve, so it was forwarded verbatim and the destination answered 401 on a garbage credential, with no error from E2B. They now throw InvalidArgumentError like any other unregistered name — on use rather than on the read, so serializing, awaiting and coercing the map itself keep working. The Python SDK was not affected.
import { Sandbox, Secret } from 'e2b'
await Sandbox.create({
iam: {
tokens: {
aws: Secret.iamToken({
audience: 'sts.amazonaws.com',
tokenType: 'JWT-SVID',
}),
},
},
network: {
rules: {
'api.example.com': [
{
// InvalidArgumentError: Network transform references iam token
// 'then', which is not registered. Registered tokens: 'aws'.
transform: ({ iam }) => ({
headers: { Authorization: `Bearer ${iam.tokens.then}` },
}),
},
],
},
},
})d79c6cd: Remove the unused stackTrace constructor parameter from error classes that never have a caller stack trace attached (TimeoutError, NotEnoughSpaceError, NotFoundError, FileNotFoundError, SandboxNotFoundError, GitUpstreamError, VolumeNotFoundError, VolumePathNotFoundError).
2be6c12: Internal refactor: the template API operations resolve their connection config through a class-level hook, so a TemplateBase subclass can carry bound connection options. No behavior change — Template / AsyncTemplate keep reading config from per-call options and environment variables. In the Python SDK the terminal template operations (build, build_in_background, get_build_status, exists, alias_exists, assign_tags, remove_tags, get_tags) became classmethods, with signatures unchanged for callers.
05aa03c: Add typed not-found errors for volumes: VolumeNotFoundError / VolumeNotFoundException (thrown when a volume is not found) and VolumePathNotFoundError / VolumePathNotFoundException (thrown when a path inside a volume is not found). All subclass the existing NotFoundError / NotFoundException, so existing catches keep working.
Nothing published for this version
Nothing published for this version
Nothing published for this version
0d507cd: Restore the http2 parameter on get_transport and get_envd_transport, which the pyqwest migration dropped in 2.38.0. http2=False again returns
http2 parameter on get_transport and get_envd_transport, which the pyqwest migration dropped in 2.38.0. http2=False again returns a transport pinned to HTTP/1.1, on its own connection pool.07eb9be: Allow a network rule's transform to be a callback, so a workload identity token from the iam option can be injected into egress requests with
07eb9be: Allow a network rule's transform to be a callback, so a workload identity token from the iam option can be injected into egress requests without the SDK ever seeing its value. The callback receives placeholder strings that the egress proxy resolves per request — iam.tokens.aws is ${e2b.identity.tokens.aws} on the wire — and referencing a token that is not registered in iam.tokens fails with InvalidArgumentError / InvalidArgumentException instead of silently sending a placeholder no token will ever replace.
updateNetwork / update_network accepts the same callbacks, but its payload carries no iam config, so token names cannot be checked there and every name resolves to its placeholder.
Token names are validated where they are registered and again before they are interpolated: a name cannot be empty or contain {, } or control characters, since the proxy reads a placeholder up to its first } and a brace in the name would resolve a different token than the one referenced.
import { Sandbox, Secret } from 'e2b'
const sandbox = await Sandbox.create({
iam: {
tokens: {
aws: Secret.iamToken({
audience: 'sts.amazonaws.com',
tokenType: 'JWT-SVID',
}),
},
},
network: {
allowOut: ({ rules }) => [...rules.keys()],
rules: {
'api.internal.example.com': [
{
transform: ({ iam }) => ({
headers: { Authorization: `Bearer ${iam.tokens.aws}` },
}),
},
],
},
},
})
from e2b import Sandbox, Secret
sandbox = Sandbox.create(
iam={
"tokens": {
"aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"),
},
},
network={
"allow_out": lambda ctx: list(ctx.rules.keys()),
"rules": {
"api.internal.example.com": [
{
"transform": lambda ctx: {
"headers": {"Authorization": f"Bearer {ctx.iam.tokens['aws']}"},
},
},
],
},
},
)
64b25bb: Add the iam option to Sandbox.create for configuring sandbox workload identity, and a Secret class with an iamToken / iam_token method for defining the workload tokens. Passing a non-empty tokens map (name → { audience, tokenType }) enables workload identity for the sandbox:
import { Sandbox, Secret } from 'e2b'
const sandbox = await Sandbox.create({
iam: {
tokens: {
aws: Secret.iamToken({
audience: 'sts.amazonaws.com',
tokenType: 'JWT-SVID',
}),
},
},
})
from e2b import Sandbox, Secret
sandbox = Sandbox.create(
iam={
"tokens": {
"aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"),
},
},
)
Plain { audience, tokenType } objects ({"audience": ..., "token_type": ...} dicts in Python) are accepted as token values too.
07eb9be: Allow a network rule's transform to be a callback, so a workload identity token from the iam option can be injected into egress requests without the SDK ever seeing its value. The callback receives placeholder strings that the egress proxy resolves per request — iam.tokens.aws is ${e2b.identity.tokens.aws} on the wire — and referencing a token that is not registered in iam.tokens fails with InvalidArgumentError / InvalidArgumentException instead of silently sending a placeholder no token will ever replace.
updateNetwork / update_network accepts the same callbacks, but its payload carries no iam config, so token names cannot be checked there and every name resolves to its placeholder.
Token names are validated where they are registered and again before they are interpolated: a name cannot be empty or contain {, } or control characters, since the proxy reads a placeholder up to its first } and a brace in the name would resolve a different token than the one referenced.
import { Sandbox, Secret } from 'e2b'
const sandbox = await Sandbox.create({
iam: {
tokens: {
aws: Secret.iamToken({
audience: 'sts.amazonaws.com',
tokenType: 'JWT-SVID',
}),
},
},
network: {
allowOut: ({ rules }) => [...rules.keys()],
rules: {
'api.internal.example.com': [
{
transform: ({ iam }) => ({
headers: { Authorization: `Bearer ${iam.tokens.aws}` },
}),
},
],
},
},
})
from e2b import Sandbox, Secret
sandbox = Sandbox.create(
iam={
"tokens": {
"aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"),
},
},
network={
"allow_out": lambda ctx: list(ctx.rules.keys()),
"rules": {
"api.internal.example.com": [
{
"transform": lambda ctx: {
"headers": {"Authorization": f"Bearer {ctx.iam.tokens['aws']}"},
},
},
],
},
},
)
64b25bb: Add the iam option to Sandbox.create for configuring sandbox workload identity, and a Secret class with an iamToken / iam_token method for defining the workload tokens. Passing a non-empty tokens map (name → { audience, tokenType }) enables workload identity for the sandbox:
import { Sandbox, Secret } from 'e2b'
const sandbox = await Sandbox.create({
iam: {
tokens: {
aws: Secret.iamToken({
audience: 'sts.amazonaws.com',
tokenType: 'JWT-SVID',
}),
},
},
})
from e2b import Sandbox, Secret
sandbox = Sandbox.create(
iam={
"tokens": {
"aws": Secret.iam_token(audience="sts.amazonaws.com", token_type="JWT-SVID"),
},
},
)
Plain { audience, tokenType } objects ({"audience": ..., "token_type": ...} dicts in Python) are accepted as token values too.
Sandbox and share it across the
filesystem, commands, and PTY modules, which now receive it instead of each
constructing their own — matching AsyncSandbox. No behavior change: the
pyqwest transport underneath is already cached process-wide per
(proxy, for_streaming), so the separate clients shared one connection pool
either way. Filesystem still builds the streaming sibling client whose
transport carries the idle read timeout, in both flavors.b048369: Move the envd HTTP API client (sandbox file transfers, health checks) onto `pyqwest` via its httpx-compatible transport adapter. envd RPC alr
b048369: Move the envd HTTP API client (sandbox file transfers, health checks) onto
pyqwest via its httpx-compatible
transport adapter. envd RPC already runs on pyqwest through connectrpc, so
all sandbox traffic now shares one HTTP stack built from the same transport
pieces (with separate connection pools per use).
The per-thread (sync) and per-loop (async) envd httpx clients are gone: the pyqwest transports are thread-safe and loop-independent, so a single client per module serves all threads and event loops.
Timeout semantics through the adapter:
files.read(format="stream")): a request_timeout
set explicitly for the call is the deadline for the whole transfer — by
default the transfer is unbounded in total, as before. A stalled stream is
reclaimed by a 60-second idle read timeout that resets on every chunk.
stream_idle_timeout keeps working on the async client (applied per
read); the sync client cannot interrupt a blocking read, so it relies on
the transport-wide idle bound and now ignores the parameter.request_timeout as a
whole-request deadline, and a streamed (file-like) upload carries no
client-side timeout (a stalled one is bounded server-side by envd's idle
read timeout) — both matching the JS SDK.files.read() as text or bytes) and buffered uploads
are bounded by request_timeout for the whole transfer (default
60 seconds), where the previous transport bounded each socket operation
and left total duration unbounded. Reading or writing a file too large to
transfer inside the deadline now raises httpx.ReadTimeout — pass a
larger request_timeout (or 0 to disable), or use
format="stream"/file-like data, for large transfers.E2B_MAX_CONNECTIONS is no longer read: it configured httpx's global
connection cap, and the last transport that took one is gone (reqwest has no
counterpart — it does not cap concurrent connections). E2B_KEEPALIVE_EXPIRY
and E2B_MAX_KEEPALIVE_CONNECTIONS keep tuning the pools.
a874ced: Move the REST API client (sandbox lifecycle, listing, templates, volumes
control plane) onto pyqwest (Rust
reqwest/hyper) via its httpx-compatible transport adapter, replacing the
httpx-native HTTPTransport/AsyncHTTPTransport. The generated httpx client
API is unchanged — only the transport underneath is swapped — so logging
event hooks, headers, and redirect handling (follow_redirects,
response.history) behave as before.
One timeout semantics change: through the adapter, request_timeout is a
deadline for the whole API call, where the previous transports applied it to
each phase (connect, read, write) separately — a slow request could exceed it
in total. For the REST API's small JSON exchanges this tightening is what
request_timeout reads as promising; 0 still disables it.
Because pyqwest transports are thread-safe and loop-independent (I/O runs on
a Rust runtime), the API connection pool is now shared process-wide per
proxy, instead of one pool per thread (sync) or per event loop (async), and
ApiClient no longer maintains per-thread/per-loop httpx client caches — a
single httpx client serves all threads and event loops.
Connection-establishment failures are retried with backoff
(E2B_CONNECTION_RETRIES, default 3), matching the connect-only retries of
the previous transports. Timeouts keep raising httpx.ReadTimeout (an
httpx.TimeoutException), as before, whether they fire while waiting for the
response head or while reading the response body, and connection, network, and
protocol failures keep raising their httpx counterparts (httpx.ConnectError,
httpx.ReadError, httpx.RemoteProtocolError).
proxy for API calls takes a URL string (e.g.
proxy="http://user:pass@localhost:8030", scheme http, https, socks5, or
socks5h), an httpx.URL, or an httpx.Proxy — including its credentials
(sent as Proxy-Authorization) and any headers configured for the proxy. The
one httpx.Proxy option pyqwest cannot express, a per-proxy ssl_context,
raises InvalidArgumentException rather than being silently dropped.
Low-level HTTP logs stay available: where enabling the httpcore logger used
to show connection-level detail, pyqwest logs one line per request on the
pyqwest.access logger and request lifecycle records on pyqwest, both at
DEBUG and off unless enabled:
import logging
logging.basicConfig()
logging.getLogger("pyqwest.access").setLevel(logging.DEBUG)
# DEBUG pyqwest.access - HTTP Request: POST https://api.e2b.app/sandboxes "HTTP/2 201 Created"
The SDK's own logger option is unchanged and independent of these.
envd traffic is not affected: RPC (commands, PTY, filesystem watch) already
runs on pyqwest via connectrpc, and the envd HTTP API (file transfers,
health checks) keeps its httpx transports.
b3a7c9f: Move template build-context uploads (to S3 presigned URLs) onto
pyqwest via its httpx-compatible
transport adapter. Content-Length framing for the streamed archive body is
preserved (S3 rejects chunked transfer encoding), and redirects stay with the
httpx client instead of being followed inside the transport. The 1-hour upload
timeout now bounds the entire upload rather than each socket operation, and
verify_ssl=False on the client is no longer honored for uploads (pyqwest
has no insecure-TLS option).
458c2c4: Move the volume content client (Volume/AsyncVolume file operations) onto
pyqwest via its httpx-compatible
transport adapter, the same stack the REST API client uses. The connection
pool is shared process-wide per proxy instead of one pool per thread (sync)
or per event loop (async), and connection-establishment failures are retried
with backoff (E2B_CONNECTION_RETRIES, default 3), as before.
For streamed volume reads (Volume.read_file(format="stream")), a stalled
stream is by default bounded by a transport-wide idle read timeout of
60 seconds that resets on every chunk (still surfaced as
httpx.ReadTimeout; matches the JS SDK's default stream idle timeout).
AsyncVolume.read_file keeps honoring an explicit stream_idle_timeout
per read (including 0 to disable); the sync client ignores it — it cannot
interrupt a blocking read. Passing request_timeout to a streamed read now
bounds the whole transfer rather than individual socket operations.
The same whole-transfer semantics apply to non-streamed operations:
read_file(format="text"/"bytes") and uploads are bounded by
request_timeout as a total deadline (default 1 hour for file content
operations), where the previous transports bounded each socket operation
and left total duration unbounded. Pass a larger request_timeout (or 0
to disable) for very large transfers on slow links.
SandboxError (JS) / SandboxException (Python) with a Failed to start MCP gateway: <stderr> message instead of a bare command exit error.domain, and the SDK uses it as the destination for volume content requests instead of the default api.<E2B_DOMAIN> host. Teams on the default cluster are unaffected and keep their configured domain.The SDK only calls glob.glob() with GLOBSTAR | DOTMATCH for template context matching; wcmatch 11.0's single breaking change affects translate() calle…
strip_ansi_escape_codes now ports the JS SDK's stripAnsi regex: OSC sequences (hyperlinks, window titles) are matched non-greedily up to the first string terminator — including sequences spanning newlines — and CSI sequences are stripped without requiring a terminator. Both implementations additionally strip the remaining ECMA-48 string controls (DCS/Sixel, SOS, PM, APC) through their string terminator so control payloads no longer leak into cleaned logs.wcmatch requirement from >=10.1,<11 to >=10.1,<12 so e2b can be installed alongside packages that already require wcmatch>=11 (for example deepagents>=0.7.0), which previously failed to resolve. The SDK only calls glob.glob() with GLOBSTAR | DOTMATCH for template context matching; wcmatch 11.0's single breaking change affects translate() callers using extended-glob capture groups, so it is a no-op here. The template glob test suite passes against 10.1, 10.2.1 and 11.0.2821fb0: Route volume content requests to a team's custom (BYOC) cluster. When a team is connected to a custom cluster, the volume create and get endp
domain, and the SDK uses it as the destination for volume content requests instead of the default api.<E2B_DOMAIN> host. Teams on the default cluster are unaffected and keep their configured domain.fromFedoraImage, fromAlpineImage, and fromArchImage base-image helpers to the Template builder (from_fedora_image, from_alpine_image, from_arch_image in the Python SDK), alongside the existing fromUbuntuImage/fromDebianImage/etc. Templates can now start from Fedora, Alpine, and Arch base images (the orchestrator identifies the distro from /etc/os-release). Fedora and Alpine default to pinned tags (fedora:44, alpine:3.24) so builds stay reproducible; Arch defaults to latest because it is a rolling release and provisioning runs pacman -Syu regardless.from_fedora_image and from_alpine_image defaults with the JS SDK: fedora:44 and alpine:3.24, replacing fedora:42 (end-of-life, so its repositories leave the normal mirror network and provisioning can fail) and alpine:3.22. Callers that omit the variant now get the same base image in both SDKs, and both tags are the ones the orchestrator's distro build tests cover. Also corrects the JS TemplateFromImage type docs, which still named the old defaults.globalThis.fetch (@hono/node-server installs its own Request, remix's installGlobals() swaps Request/Blob/File, web-streams-polyfill swaps ReadableStream, jsdom-style test environments bring their own copies), and values also cross realms — so a perfectly good Request, Blob or ReadableStream could fail the SDK's instanceof checks and take the wrong branch. This fixes: every API call crashing with Failed to parse URL from [object Request]; the abort signal of such a Request being ignored while it waited for an in-flight slot; uploads of a foreign Blob or ReadableStream — including the body of such a Request — silently containing the text "[object Blob]"/"[object ReadableStream]"; gzipped uploads of a foreign stream hanging; a foreign stream being buffered into memory instead of streamed; and volume.readFile() returning empty data.1504fbc: Add fromFedoraImage, fromAlpineImage, and fromArchImage base-image helpers to the Template builder (from_fedora_image, from_alpine_image, fro
fromFedoraImage, fromAlpineImage, and fromArchImage base-image helpers to the Template builder (from_fedora_image, from_alpine_image, from_arch_image in the Python SDK), alongside the existing fromUbuntuImage/fromDebianImage/etc. Templates can now start from Fedora, Alpine, and Arch base images (the orchestrator identifies the distro from /etc/os-release). Fedora and Alpine default to pinned tags (fedora:44, alpine:3.24) so builds stay reproducible; Arch defaults to latest because it is a rolling release and provisioning runs pacman -Syu regardless.from_fedora_image and from_alpine_image defaults with the JS SDK: fedora:44 and alpine:3.24, replacing fedora:42 (end-of-life, so its repositories leave the normal mirror network and provisioning can fail) and alpine:3.22. Callers that omit the variant now get the same base image in both SDKs, and both tags are the ones the orchestrator's distro build tests cover. Also corrects the JS TemplateFromImage type docs, which still named the old defaults.e2b/sandbox/mcp.py with datamodel-code-generator 0.64.0: the MCP server option types now use builtin generics (list[str], dict[str, Any]) and are closed TypedDicts, mirroring the spec's additionalProperties: false. Raises the typing-extensions floor to >=4.10.0, the first release accepting PEP 728's closed.FileType.SYMLINK to the sandbox filesystem types. Newer envd versions report symlinks with a dedicated FILE_TYPE_SYMLINK entry type; previously the SDKs treated it as unknown, so files.list() silently omitted symlink entries and getInfo()/get_info() returned an undefined/None type for them. Symlinks now surface as FileType.SYMLINK ('symlink') with symlinkTarget/symlink_target populated, in JS and both sync and async Python.runInNewStackTraceContext, runInStackTraceOverrideContext and their Python equivalents) is removed.Network connection lost as a dropped sandbox connection so a sandbox killed mid-request surfaces as the health-checked TimeoutError (matching Node/Bun/Deno), and fix streaming downloads releasing their pooled connection twice when cancelled while a read was in flightfetch ignores a Content-Length header on stream bodies and fell back to Transfer-Encoding: chunked, which S3-compatible presigned upload URLs reject (see #1243). Template.build uploads now stream the spooled archive through undici's fetch, which honors the header on every runtime, falling back to the global fetch where undici isn't resolvable.Sandbox.getHost() documentation example so it can be copy-pasted. The @example called sandbox.commands.exec(...), which is not a method on the Commands class (it exposes run), so running the snippet threw TypeError: sandbox.commands.exec is not a function. It now uses sandbox.commands.run(..., { background: true }), allowing the long-running HTTP server to start before the example calls getHost(). Documentation only, no behavior change.SandboxTimeoutRequest/SandboxSnapshotRequest/SandboxRefreshRequest request schemas, SandboxNetworkConfig and SandboxIam workload-identity models, the FILE_TYPE_SYMLINK filesystem entry type, and deprecation of access-token auth in favor of API keys. Anything the upstream specs mark x-not-implemented: true (currently the SOCKS5 egress-proxy config) is excluded from the generated clients. Generated Python client models now list fields in spec order instead of alphabetical order (the tag filtering moved from a custom script to Redocly CLI); construct them with keyword arguments if you don't alreadytar dependency to 7.5.19 to pull in upstream fixes for node-tar denial-of-service vulnerabilities (GHSA advisories covering PAX parsing, negative entry sizes, and unbounded decompression)…the FILE_TYPE_SYMLINK filesystem entry type, and deprecation of access-token auth in favor of API keys. Anything the upstream specs mark x-not-impleme…
00253c3: Migrate the sandbox RPC layer (commands, PTY, filesystem watch) from the
vendored e2b_connect client to the official Connect RPC client for Python
(connectrpc), whose HTTP transport
is pyqwest (Rust reqwest/hyper), and switch the envd protobuf messages from
Google's protobuf runtime to Buf's
protobuf-py.
Closing a command or watch stream early now sends RST_STREAM to the server,
so abandoned streams no longer leak on the shared HTTP/2 connection, and peer
resets surface as typed errors instead of ambiguous EOFs. The REST API and
file upload/download keep using httpx.
Notes:
protobuf package, removing a common
source of dependency conflicts with other libraries that pin it.e2b_connect module is no longer shipped with the package. Code that
imported it directly should use connectrpc (ConnectError, Code)
instead; SDK exception types (SandboxException, TimeoutException, ...)
are unchanged.e2b.envd.*.*_pb2 modules were replaced by protobuf-py
equivalents (e2b.envd.process.process_pb,
e2b.envd.filesystem.filesystem_pb) with a different message API.E2B_CONNECTION_RETRIES, default 3) now retry only failures establishing the connection — before the request
could have reached envd — with exponential backoff. Unary RPCs are no
longer replayed when the connection drops mid-request, which could
re-execute a delivered call (e.g. re-send process input); such drops
surface as errors immediately, the way they always did for streaming calls.proxy option applies to sandbox RPC calls the same way it does to the
REST API and file transfer requests. URL strings, httpx.URL, and
httpx.Proxy values keep working (credentials in the URL or in
httpx.Proxy(auth=...)); httpx.Proxy custom headers and ssl_context
are not supported for RPC calls and raise InvalidArgumentException.CommandResult.error (and CommandHandle.error) is now None when a
command finishes without an error, matching the declared Optional[str]
type and the JS SDK's error?: string. It used to be "" on success —
code comparing result.error == "" or treating it as always-str should
check for None/falsiness instead.commands.run/connect, PTY,
files.watch_dir), request_timeout now bounds opening the stream — the
wait until envd confirms with a start event, matching the JS SDK's
requestTimeoutMs — and raises TimeoutException when exceeded. The
running stream is bounded by the command/watch timeout (as before). In
the sync SDK there is no way to interrupt the blocking wait, so
request_timeout is not applied to opening the stream — both stream setup
and the running stream are bounded by timeout (unlimited when 0).E2B_MAX_CONNECTIONS no longer applies to sandbox RPC traffic: the new
transport bounds only idle connections per host (E2B_KEEPALIVE_EXPIRY,
E2B_MAX_KEEPALIVE_CONNECTIONS), not the total number of open
connections. It still applies to the REST API and file transfers.4fcf7cb: Add FileType.SYMLINK to the sandbox filesystem types. Newer envd versions report symlinks with a dedicated FILE_TYPE_SYMLINK entry type; previously the SDKs treated it as unknown, so files.list() silently omitted symlink entries and getInfo()/get_info() returned an undefined/None type for them. Symlinks now surface as FileType.SYMLINK ('symlink') with symlinkTarget/symlink_target populated, in JS and both sync and async Python.
runInNewStackTraceContext, runInStackTraceOverrideContext and their Python equivalents) is removed.SandboxTimeoutRequest/SandboxSnapshotRequest/SandboxRefreshRequest request schemas, SandboxNetworkConfig and SandboxIam workload-identity models, the FILE_TYPE_SYMLINK filesystem entry type, and deprecation of access-token auth in favor of API keys. Anything the upstream specs mark x-not-implemented: true (currently the SOCKS5 egress-proxy config) is excluded from the generated clients. Generated Python client models now list fields in spec order instead of alphabetical order (the tag filtering moved from a custom script to Redocly CLI); construct them with keyword arguments if you don't already95e4dc2: Add sandbox.fork() and Sandbox.fork(sandboxId) for forking a running sandbox. The sandbox is checkpointed in place (briefly paused, snapshotted with its full memory state, and resumed — its ID and expiration stay untouched) and count new sandboxes are created from that snapshot. Each fork succeeds or fails independently: the returned array contains one entry per requested fork, either a running Sandbox instance or an Error (Promise.allSettled-style). Per-fork error codes map to the same error classes as other API errors (e.g. 429 to RateLimitError).
const sandbox = await Sandbox.create()
const [fork1, fork2] = await sandbox.fork({ count: 2, timeoutMs: 60_000 })
if (fork1 instanceof Sandbox) {
await fork1.commands.run('echo "hello from fork"')
}
95e4dc2: Add sandbox.fork() / Sandbox.fork(sandbox_id) (and the AsyncSandbox equivalents) for forking a running sandbox. The sandbox is checkpointed i
95e4dc2: Add sandbox.fork() / Sandbox.fork(sandbox_id) (and the AsyncSandbox equivalents) for forking a running sandbox. The sandbox is checkpointed in place (briefly paused, snapshotted with its full memory state, and resumed — its ID and expiration stay untouched) and count new sandboxes are created from that snapshot. Each fork succeeds or fails independently: the returned list contains one entry per requested fork, either a running sandbox instance or an exception. Per-fork error codes map to the same exception classes as other API errors (e.g. 429 to RateLimitException).
sandbox = Sandbox.create()
fork1, fork2 = sandbox.fork(count=2, timeout=60)
if isinstance(fork1, Sandbox):
fork1.commands.run('echo "hello from fork"')
2c77fc0: Add a name filter to Sandbox.listSnapshots() / Sandbox.list_snapshots().
The filter accepts a snapshot name or ID, optionally tag-qualified (e.g.
"my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). Unknown names
return an empty list rather than an error. It can be combined with the existing
sandboxId / sandbox_id filter (results must match both).
// JS/TS
const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' })
const snapshots = await paginator.nextItems()
# Python (sync)
paginator = Sandbox.list_snapshots(name="my-snapshot")
snapshots = paginator.next_items()
# Python (async)
paginator = AsyncSandbox.list_snapshots(name="my-snapshot")
snapshots = await paginator.next_items()
2c77fc0: Add a name filter to Sandbox.listSnapshots() / Sandbox.list_snapshots().
2c77fc0: Add a name filter to Sandbox.listSnapshots() / Sandbox.list_snapshots().
The filter accepts a snapshot name or ID, optionally tag-qualified (e.g.
"my-snapshot", "my-team/my-snapshot" or "my-snapshot:v1"). Unknown names
return an empty list rather than an error. It can be combined with the existing
sandboxId / sandbox_id filter (results must match both).
// JS/TS
const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' })
const snapshots = await paginator.nextItems()
# Python (sync)
paginator = Sandbox.list_snapshots(name="my-snapshot")
snapshots = paginator.next_items()
# Python (async)
paginator = AsyncSandbox.list_snapshots(name="my-snapshot")
snapshots = await paginator.next_items()
integration connection option with a set-once, process-wide ConnectionConfig.setIntegration() (JS) / ConnectionConfig.set_integration() (Python). Integrations wrapping the SDK call it once at startup and every request is attributed via the User-Agent header — no more threading the option through individual SDK calls. The method is internal and hidden from docs. The integration option on ConnectionConfigOpts (JS) and the integration keyword argument on ConnectionConfig (Python) are removed; ConnectionConfigOpts remains as a deprecated alias of ConnectionOpts. In both SDKs, an explicitly provided User-Agent header now always takes precedence over the SDK-built one, while SDK-built values are recomputed whenever a config is rebuilt so they always reflect the current integration.e6c4e7e: Move the Connect/Protobuf runtime dependencies off the 2.0.0-rc.3 pre-release pin to the stable line: @connectrpc/connect and @connectrpc/connect-web upgrade to ^2.1.2, and @bufbuild/protobuf upgrades from ^2.6.2 to ^2.12.1. No public API changes — the sandbox filesystem and command RPCs continue to use the same Connect transport configuration.
49367c8: Switch the build tooling from tsup to tsdown. The published artifacts are unchanged: the SDK still ships dist/index.js (CJS), dist/index.mjs (ESM) and dist/index.d.ts/dist/index.d.mts, and the CLI still ships an executable dist/index.js with its dist/templates.
engines.node for both packages is set to >=20.18.1 <21 || >=22 (Node 20.18.1+, or 22 and above — keeping the minimum required by undici while excluding the end-of-life Node 21 line).
7474d90: Fix inverted no_install_recommends docstring on TemplateBuilder.apt_install(). The docstring described the parameter as "Whether to install r
7474d90: Fix inverted no_install_recommends docstring on TemplateBuilder.apt_install().
The docstring described the parameter as "Whether to install recommended
packages", but the code passes apt-get's --no-install-recommends flag when the
argument is True, which skips recommended packages, the opposite of what the
docs claimed. The docstring now describes the real behavior. No behavior change.
99e536f: fix(python-sdk): stop leaking per-call proxy connection pools in volume content clients
The volume content clients passed both proxy and the shared cached
transport to httpx, so with a proxy configured every volume operation mounted
a fresh, never-closed proxy transport. The proxy is already part of the cached
transport, so the client-level proxy argument is now dropped. Volume
transports also gained connect-level retries, matching the other transports.
…are removed; ConnectionConfigOpts remains as a deprecated alias of ConnectionOpts. In both SDKs, an explicitly provided User-Agent header now always t…
integration connection option with a set-once, process-wide ConnectionConfig.setIntegration() (JS) / ConnectionConfig.set_integration() (Python). Integrations wrapping the SDK call it once at startup and every request is attributed via the User-Agent header — no more threading the option through individual SDK calls. The method is internal and hidden from docs. The integration option on ConnectionConfigOpts (JS) and the integration keyword argument on ConnectionConfig (Python) are removed; ConnectionConfigOpts remains as a deprecated alias of ConnectionOpts. In both SDKs, an explicitly provided User-Agent header now always takes precedence over the SDK-built one, while SDK-built values are recomputed whenever a config is rebuilt so they always reflect the current integration.2b7dd17: Add a gzip option to the template copy layer to control whether copied
files are gzipped before upload.
Gzip is enabled by default (matching the previous behavior). Pass
gzip: false (gzip=False in Python) to upload an uncompressed tar archive
instead — useful when copying already-compressed files where gzipping adds
CPU cost without shrinking the payload.
// JS/TS
template.copy('model.bin', '/app/', { gzip: false })
# Python
template.copy('model.bin', '/app/', gzip=False)
hasNext/has_next, nextToken/next_token, and reading the x-next-token header) while each concrete paginator implements nextItems/next_items to fetch its own page, so future list endpoints can add pagination by subclassing it without reimplementing the bookkeeping. No public API changes.ConnectionConfig instead of exposing it through sandbox, template, volume, or per-call API option types.Sandbox.list() documentation across both SDKs: it returns a paginator (SandboxPaginator / AsyncSandboxPaginator), not a list, and by default the server returns sandboxes in both running and paused states. The docstrings now describe the return type accurately and show how to iterate pages via paginator.next_items() / await paginator.nextItems() while paginator.has_next / paginator.hasNext is true. No behavior change.Content-Length required by S3 presigned URLs. Temp-file cleanup is now best-effort, so a cleanup failure after the upload no longer masks a successful upload as an error (nor overwrites the real upload error on failure). The Python SDK now uploads the archive with a 1-hour default timeout (overridable via request_timeout) instead of the 60s general API timeout, matching the JS SDK and preventing large uploads from timing out.2b7dd17: Add a gzip option to the template copy layer to control whether copied files are gzipped before upload.
2b7dd17: Add a gzip option to the template copy layer to control whether copied
files are gzipped before upload.
Gzip is enabled by default (matching the previous behavior). Pass
gzip: false (gzip=False in Python) to upload an uncompressed tar archive
instead — useful when copying already-compressed files where gzipping adds
CPU cost without shrinking the payload.
// JS/TS
template.copy('model.bin', '/app/', { gzip: false })
# Python
template.copy('model.bin', '/app/', gzip=False)
bb45f18: Introduce a generic, reusable paginator base class and migrate the sandbox and snapshot list paginators onto it. The base owns the shared cursor-based pagination state (hasNext/has_next, nextToken/next_token, and reading the x-next-token header) while each concrete paginator implements nextItems/next_items to fetch its own page, so future list endpoints can add pagination by subclassing it without reimplementing the bookkeeping. No public API changes.
f160f08: Keep integration user-agent attribution on ConnectionConfig instead of exposing it through sandbox, template, volume, or per-call API option types.
8b8a224: feat(python-sdk): add a logger option for request/debug logging
You can now pass a standard library logging.Logger to Sandbox.create /
AsyncSandbox.create (and the static Sandbox.connect(sandbox_id, ...)) to
route that sandbox's request/response logs to your own logger. The logger is
stored on the sandbox and propagates to all of its later operations —
including control-plane calls such as kill, pause, set_timeout, and
get_info. Matching the JavaScript SDK, logger is a construction-time option
and is not a per-request parameter that those methods accept from the
caller. The stdlib logging.Logger is used directly as the adapter instead of
a custom interface.
The logger is wired into the API client, the envd client, and the RPC
(ConnectRPC) path. Mirroring the JS SDK: requests log at INFO, successful API
and unary RPC responses at INFO, streamed RPC messages at DEBUG, and failed
API responses (status >= 400) at ERROR. When no logger is supplied, the SDK
emits no request/response logging at all (also matching the JS SDK).
Volume content operations continue to accept logger per call via
VolumeApiParams, matching the JS Volume API.
import logging
from e2b import Sandbox
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("my-app.e2b")
sbx = Sandbox.create(logger=logger)
sbx.commands.run("echo hello") # request/response logged via `logger`
a6b1cf4: fix(python-sdk): strip colon-separated SGR escape codes in build logs
strip_ansi_escape_codes now strips CSI parameters that use colons as well as
semicolons, matching the JavaScript SDK's stripAnsi. Modern terminals emit
colon-separated SGR sequences (e.g. 256-color \x1b[38:5:82m, truecolor
\x1b[38:2::r:g:bm, and curly underlines \x1b[4:3m), which previously leaked
literal escape garbage into template build log messages on the Python side.
c385566: Correct Sandbox.list() documentation across both SDKs: it returns a paginator (SandboxPaginator / AsyncSandboxPaginator), not a list, and by default the server returns sandboxes in both running and paused states. The docstrings now describe the return type accurately and show how to iterate pages via paginator.next_items() / await paginator.nextItems() while paginator.has_next / paginator.hasNext is true. No behavior change.
bb16968: Template builds: the build-context tar archive is now spooled to a temporary file and streamed from disk during upload instead of being held in memory (JS and Python, sync and async), while keeping the explicit Content-Length required by S3 presigned URLs. Temp-file cleanup is now best-effort, so a cleanup failure after the upload no longer masks a successful upload as an error (nor overwrites the real upload error on failure). The Python SDK now uploads the archive with a 1-hour default timeout (overridable via request_timeout) instead of the 60s general API timeout, matching the JS SDK and preventing large uploads from timing out.
7e7e951: Add an object form to the sandbox lifecycle.onTimeout (on_timeout in Python)
that controls the snapshot kind taken when a sandbox auto-pauses on timeout, via
keepMemory (keep_memory).
onTimeout now accepts either the existing bare action ('pause' / 'kill') or
the object form. The object form is a discriminated union on action:
keepMemory is only accepted alongside action: 'pause' — pairing it with
action: 'kill' is a compile-time type error (and is rejected at runtime for
untyped callers). When keepMemory is false, a timeout auto-pause drops the
in-memory state and persists only the filesystem (a filesystem-only snapshot);
resuming such a sandbox cold-boots (reboots) it from disk, losing running
processes and open connections. Defaults to true (full memory snapshot). It
cannot be combined with auto-resume: auto-resume wakes a paused sandbox on
inbound traffic by restoring its memory snapshot in place, and a filesystem-only
snapshot has no memory to restore (resuming cold-boots it), so it must be resumed
explicitly. The bare string form is unchanged.
# Python
sbx = Sandbox.create(
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}}
)
// JS/TS
const sbx = await Sandbox.create({
lifecycle: { onTimeout: { action: 'pause', keepMemory: false } },
})
cb5a387: Add a keepMemory (keep_memory in Python) option to pause for
filesystem-only snapshots.
When keepMemory is false, pausing drops the in-memory state and captures
only the filesystem (no memory snapshot); resuming such a snapshot cold-boots
(reboots) the sandbox from disk, losing running processes and open connections.
Defaults to true (full memory snapshot), so existing callers are unaffected.
# Python
sbx.pause(keep_memory=False) # filesystem-only snapshot
// JS/TS
await sandbox.pause({ keepMemory: false }) // filesystem-only snapshot
WatchHandle now awaits async onEvent/onExit callbacks. A rejecting async onEvent is routed to onExit and stops the watch instead of becoming an unhandled promise rejection that can crash Node, and async callbacks get backpressure/ordering — matching CommandHandle.WatchHandle.get_new_events() and stop() now send a request timeout (default 60s, overridable via request_timeout) so a stalled call can't hang the thread forever, and include the authentication header so the polling/stop calls aren't sent unauthenticated on older envd.AsyncWatchHandle now invokes on_exit when the stream ends cleanly (with None) and when stop() is called, in addition to on error — matching the JS SDK.7e7e951: Add an object form to the sandbox lifecycle.onTimeout (on_timeout in Python) that controls the snapshot kind taken when a sandbox auto-pauses
7e7e951: Add an object form to the sandbox lifecycle.onTimeout (on_timeout in Python)
that controls the snapshot kind taken when a sandbox auto-pauses on timeout, via
keepMemory (keep_memory).
onTimeout now accepts either the existing bare action ('pause' / 'kill') or
the object form. The object form is a discriminated union on action:
keepMemory is only accepted alongside action: 'pause' — pairing it with
action: 'kill' is a compile-time type error (and is rejected at runtime for
untyped callers). When keepMemory is false, a timeout auto-pause drops the
in-memory state and persists only the filesystem (a filesystem-only snapshot);
resuming such a sandbox cold-boots (reboots) it from disk, losing running
processes and open connections. Defaults to true (full memory snapshot). It
cannot be combined with auto-resume: auto-resume wakes a paused sandbox on
inbound traffic by restoring its memory snapshot in place, and a filesystem-only
snapshot has no memory to restore (resuming cold-boots it), so it must be resumed
explicitly. The bare string form is unchanged.
# Python
sbx = Sandbox.create(
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}}
)
// JS/TS
const sbx = await Sandbox.create({
lifecycle: { onTimeout: { action: 'pause', keepMemory: false } },
})
cb5a387: Add a keepMemory (keep_memory in Python) option to pause for
filesystem-only snapshots.
When keepMemory is false, pausing drops the in-memory state and captures
only the filesystem (no memory snapshot); resuming such a snapshot cold-boots
(reboots) the sandbox from disk, losing running processes and open connections.
Defaults to true (full memory snapshot), so existing callers are unaffected.
# Python
sbx.pause(keep_memory=False) # filesystem-only snapshot
// JS/TS
await sandbox.pause({ keepMemory: false }) // filesystem-only snapshot
WatchHandle now awaits async onEvent/onExit callbacks. A rejecting async onEvent is routed to onExit and stops the watch instead of becoming an unhandled promise rejection that can crash Node, and async callbacks get backpressure/ordering — matching CommandHandle.WatchHandle.get_new_events() and stop() now send a request timeout (default 60s, overridable via request_timeout) so a stalled call can't hang the thread forever, and include the authentication header so the polling/stop calls aren't sent unauthenticated on older envd.AsyncWatchHandle now invokes on_exit when the stream ends cleanly (with None) and when stop() is called, in addition to on error — matching the JS SDK.allowNetworkMounts/allow_network_mounts option to filesystem directory watching. When enabled, paths on network filesystem mounts (NFS, CIFS, SMB, FUSE) can be watched — they are rejected by default because events on network mounts may be unreliable or not delivered at all. Requires envd 0.6.4 or later; watching with this option against an older sandbox raises a template error.Sandbox.getMetrics() sending start and end as path parameters instead of query parameters, which caused the requested time range to be silently ignoredVolume.readFile returning undefined instead of an empty Blob/ReadableStream for empty files, and apply the documented 60s default request timeout to volume content requests.boolean from the Sandbox.kill() instance method (true if the sandbox was killed, false if it was not found), matching the static Sandbox.kill() and the Python SDK.Content-Length: 0) instead of treating them as successful.2a98cce: Fix CommandHandle (sync and async) recording the command result after yielding the flushed end-event chunks. The decoders are now flushed and
CommandHandle (sync and async) recording the command result after yielding the flushed end-event chunks. The decoders are now flushed and the CommandResult is recorded before the trailing chunks are yielded, so a consumer that stops iterating on the first flushed chunk still observes the exit code.8171a03: Fix Python SDK header precedence so a custom Authorization passed via api_headers is no longer overwritten by the deprecated access_token. Th…
60feee3: Stream uploads instead of buffering streaming input entirely in memory:
Sandbox.files.write() / write_files(): ReadableStream data (JS, outside the browser) and file-like objects (Python) are streamed to the sandbox, including when gzip is enabled (compression now happens chunk by chunk). useOctetStream/use_octet_stream now defaults to auto-detect — octet-stream is used when any entry is streamable (so streamed uploads aren't silently buffered) and multipart/form-data otherwise; browsers always use multipart/form-data. A streamed upload is bounded by a per-chunk timeout on the wire (Python's per-write httpx timeout, default the request timeout); a stalled upload that this can't observe is bounded server-side. On Python's AsyncSandbox, the blocking file reads and gzip compression of a streamed upload now run in a worker thread so a large upload doesn't stall the event loop.Sandbox.files.read(format="stream"): the request timeout now bounds only the initial handshake instead of killing the stream while it's being consumed. The body is bounded by a per-chunk idle timeout on the wire (streamIdleTimeoutMs in JS, stream_idle_timeout in Python, default the request timeout — 60s — 0/None to disable): it aborts only when the server stops sending mid-stream, and a slow or paused consumer never trips it (a held-but-unread stream is reclaimed server-side). Use signal (JS) to cancel an in-flight stream. A dropped connection during the stream handshake now surfaces the same typed, health-checked error as non-stream reads. The stream holds a pooled connection until it is consumed to the end, cancelled/closed, errors, or the idle timeout fires — consume it fully, use the context manager, or close it.Sandbox.files.read(format="stream"): the response body is now streamed from the sandbox instead of being downloaded into memory before iteration (sync and async).Sandbox.files.read() with blob or stream format now returns an empty Blob/ReadableStream for empty files instead of "".8171a03: Fix Python SDK header precedence so a custom Authorization passed via api_headers is no longer overwritten by the deprecated access_token. The deprecated access token is now applied before api_headers, matching the JS SDK where a custom Authorization wins.
c1415f3: Stream volume file uploads and downloads instead of buffering in memory:
Volume.writeFile() / Volume.write_file(): ReadableStream data (JS, outside the browser) and file-like objects (Python) are now streamed to the API in chunks.Volume.readFile(format="stream") / read_file(format="stream"): the request timeout now bounds only the initial handshake instead of killing the stream while it's being consumed (Python disables the read timeout; JS bounds the handshake and supports signal to cancel an in-flight stream). A dropped connection during the stream handshake now surfaces the same typed, health-checked error as non-stream reads.726ced6: Fix duplicate logo on NPM/PyPI by switching to element.
<picture> element.f3e7f33: Tidy up SDK authentication and deprecate the access token in ConnectionConfig.
f3e7f33: Tidy up SDK authentication and deprecate the access token in ConnectionConfig.
accessToken (JS) / access_token (Python) option on ConnectionConfig. It still works exactly as before — when set (or via E2B_ACCESS_TOKEN), the Authorization: Bearer header is still sent — but you should pass custom auth through apiHeaders instead, e.g. new ConnectionConfig({ apiHeaders: { Authorization: 'Bearer <token>' } }).https://e2b.dev/dashboard?tab=keys). In JS this is controlled by a requireApiKey option (default true) so callers that authenticate differently — like the CLI hitting /teams with an access token — can opt out; in Python the API key is always required.requireAccessToken (JS) and require_access_token (Python). No caller ever set it to a non-default value, so behavior is unchanged./teams endpoint through apiHeaders instead of the deprecated option.ConnectionConfig: EnvdApiClient now owns its own accessToken field and sets the X-Access-Token header itself.0a5d524: Update package logos with theme-aware dark/light variants for GitHub.
Match the exact listening port in wait_for_port (via ss's sport filter) so e.g. port 80 no longer matches 8080.
706c553: Template SDK fixes:
wait_for_port (via ss's sport filter) so e.g. port 80 no longer matches 8080.wait_for_url, wait_for_file, and wait_for_process.remove, rename, make_dir, make_symlink, git_clone (URL/branch/path), and the devcontainer helpers so values with spaces or shell metacharacters work correctly.COPY instruction so failed-step stack traces stay aligned after copy() with multiple sources or copy_items().432c091: Add integration attribution options that append integration metadata to the SDK User-Agent.
1d5259c: Fix a batch of connection-handling bugs in the JS and Python SDKs:
request_timeout is now applied to control-plane (E2B API) requests; previously the underlying httpx client was built with no timeout at all.debug: false explicitly now overrides the E2B_DEBUG=true environment variable instead of being ignored.TypeError when a response contains protobuf int64 (bigint) fields; they are now logged as strings.e88ae33: Fix signed URL expiration edge cases in uploadUrl/downloadUrl (upload_url/download_url):
InvalidArgumentException when use_signature_expiration is passed for an unsecured sandbox, matching the JS SDK behavior (which now throws InvalidArgumentError instead of a plain Error).0 now produces an immediately expiring URL instead of silently creating a never-expiring one.78c200a: Allow disabling client-side API key format validation. Set the E2B_VALIDATE_API_KEY environment variable to false, or pass the validateApiKey: false (JS) / validate_api_key=False (Python) connection option, when your deployment issues API keys that don't match the default e2b_ format.
cb061d2: Fix several command and PTY streaming issues:
None instead of empty strings for unset tag and cwd fields in commands.list()envs dict when applying default TERM/LANG/LC_ALL values in pty.create()5de9bc2: Map transport-level timeouts from httpcore (e.g. a stream read timeout) to TimeoutException. When iterating over a long-running command's output, the underlying HTTP read timeout (set to the command timeout) races with the server's own deadline_exceeded response; whichever fires first won, so the client could leak a raw httpcore.ReadTimeout instead of a TimeoutException. Both cases now surface a consistent, actionable TimeoutException.
82add5b: Raise a typed, actionable error when the sandbox dies while a request is in flight. When the connection is dropped mid-request (streaming RPC calls — commands, PTY, directory watch — and filesystem read/write), the SDKs now probe the sandbox health endpoint: if the sandbox is confirmed gone, a TimeoutError (JS) / TimeoutException (Python) is raised stating the sandbox was killed or reached its end of life — consistent with how requests to an already-dead sandbox surface. In all other cases the original error propagates unchanged.
5ea287b: Fix write() / write_files() returning WriteInfo.type as a raw string instead of the FileType enum. Make EntryInfo.modified_time timezone-aware (UTC) and normalize naive volume atime/mtime/ctime timestamps to UTC. Make the gzip=True upload option imply the application/octet-stream upload path so it is no longer silently ignored on the default multipart/form-data path.
e0ed071: Anchor file-metadata validation regexes with \A/\Z instead of ^/$. In Python, $ also matches just before a trailing newline, so metadata keys or values ending in \n passed client-side validation (unlike the JS SDK) and then failed later with an opaque low-level HTTP "illegal header value" error. They are now rejected upfront with InvalidArgumentException.
7cec36d: Drop the HTTP read timeout on streaming calls (commands, PTY, watch). It was set to the command timeout, so it raced the server's own deadline_exceeded response and could intermittently leak a raw httpcore.ReadTimeout instead of a TimeoutException. The command timeout is now enforced solely server-side via the connect-timeout-ms header, matching the JS SDK which has no per-chunk read timeout.
b52eb3c: Skip the control plane request in Sandbox.connect() when running in debug mode, matching the behavior of Sandbox.create(). In the Python SDK, Sandbox.connect() now also normalizes missing envd and traffic access tokens to None instead of leaking the Unset sentinel, which previously broke download_url()/upload_url() for non-secure sandboxes.
e873ee9: Add an allowNetworkMounts/allow_network_mounts option to filesystem directory watching. When enabled, paths on network filesystem mounts (NFS
allowNetworkMounts/allow_network_mounts option to filesystem directory watching. When enabled, paths on network filesystem mounts (NFS, CIFS, SMB, FUSE) can be watched — they are rejected by default because events on network mounts may be unreliable or not delivered at all. Requires envd 0.6.4 or later; watching with this option against an older sandbox raises a template error.32880d6: Key per-event-loop async HTTP transport and client caches by the loop object (held weakly) instead of id(loop). CPython can reuse the id of a closed loop almost immediately, so sequential event loops (for example repeated asyncio.run(...) calls) could inherit a transport or client bound to a previous, closed loop. Cache entries are now also released automatically when their loop is garbage collected.
0b0c728: Fix several issues in the volume content API client:
AsyncVolume HTTP transports are now cached per event loop (and per proxy) instead of a process-wide singleton, and sync transports are cached per thread.list, make_dir, get_info, update_metadata, remove) now respect request_timeout (60s by default) instead of running with httpx timeouts disabled.E2B_ACCESS_TOKEN environment variable; it requires the volume token, matching the JS SDK.VolumeConnectionConfig no longer mutates the caller's headers dict.1328d9f: Retry connection establishment on the HTTP/2 transports used for the E2B API and envd clients (3 retries by default, configurable via the E2B_CONNECTION_RETRIES environment variable).
961ffba: feat(sdks): expose user-defined file metadata on sandbox.files
Adds a metadata option to file uploads (write / writeFiles / write_files) and surfaces persisted metadata on every EntryInfo / WriteInfo returned by getInfo, list, rename, and write responses. On upload, metadata is sent as X-Metadata-<key>: <value> request headers; envd persists the values as extended attributes in the user.e2b. xattr namespace and returns them on subsequent filesystem reads (including user.e2b.* xattrs set out-of-band). Keys are sent as HTTP header names and are lowercased by the sandbox; metadata is validated client-side (keys must be valid HTTP header tokens, values must be printable US-ASCII) and invalid input raises InvalidArgumentError / InvalidArgumentException. The same metadata map is applied to every file in a multi-file upload. Requires envd 0.6.2 or later.
da85b1e: Add an includeEntry/include_entry option to filesystem directory watching. When enabled, each FilesystemEvent carries the affected entry's EntryInfo (best-effort; left unset for events where the path no longer exists, such as remove/rename-away). Requires envd 0.6.3 or later; watching with this option against an older sandbox raises a template error.
7dc861f: fix: align behavior between the JS and Python SDKs
Python SDK:
commands.send_stdin and CommandHandle.send_stdin now accept bytes in addition to str, and the handle's send_stdin / close_stdin now accept a request_timeout.git.reset now accepts a typed GitResetMode and its validation error matches the JS SDK wording/ordering. GitResetMode is now exported.sandbox_url is now propagated through get_api_params.Template.from_image() now raises when only one of username / password is provided.get_info() no longer carries the envd access token on the returned SandboxInfo (the _envd_access_token field was unused), matching the JS SDK which strips it from getInfo.get_metrics() now raises TemplateException (was SandboxException) with the same message as the JS SDK when the sandbox is too old.JS SDK:
Sandbox.getInfo() now includes sandboxDomain, matching the Python SDK's single get_info. getFullInfo is deprecated and now just wraps getInfo (it no longer returns the envd access token).Sandbox.getMetrics() now returns [] in debug mode, matching the Python SDK. The debug short-circuit for getMetrics / kill is implemented on both the instance and static methods, so it applies consistently whether called as Sandbox.kill(sandboxId) or sandbox.kill().Template.fromImage() now requires both username and password when registry credentials are provided.Template.getBuildStatus() now defaults logsOffset to 0.requestTimeoutMs: 0 now explicitly disables the request timeout.getMetrics() now throws TemplateError (was SandboxError) when the sandbox is too old to support metrics.554dc88: Create Python SDK sync HTTP clients from the calling thread, including sandbox envd clients and API clients used by template uploads.
proxy connection parameter to route SDK requests through an HTTP proxy, matching the Python SDK. When set, it applies to API requests, all requests made to the returned sandbox, and volume requests.proxy not being applied to volume content requests. Volume.create/Volume.connect now store the proxy on the returned instance, so instance methods (list, readFile, writeFile, makeDir, getInfo, updateMetadata, remove, …) route through it without having to pass proxy on every call. A per-call proxy still takes precedence.08012ee: feat: add sendStdin/send_stdin and closeStdin/close_stdin to CommandHandle
08012ee: feat: add sendStdin/send_stdin and closeStdin/close_stdin to CommandHandle
You can now send and close stdin directly on a background command handle instead of going through sandbox.commands with the command's PID.
ca18220: Use components['schemas'][...] instead of verbose paths[...] traversal for OpenAPI-generated type references.
1d5259c: Fix a batch of connection-handling bugs in the JS and Python SDKs:
request_timeout is now applied to control-plane (E2B API) requests; previously the underlying httpx client was built with no timeout at all.debug: false explicitly now overrides the E2B_DEBUG=true environment variable instead of being ignored.TypeError when a response contains protobuf int64 (bigint) fields; they are now logged as strings.44c1e9f: Keep sync sandbox envd clients thread-local when a sandbox object is used from worker threads.
073661a: Add API-only custom header options for the JavaScript and Python SDKs.
wait helper (js-sdk), asSandboxTemplate/asHeadline/selectOption/basicDockerfile (cli), and format_execution_timeout_error (python-sdk). No public API changes.RateLimitError (JS) / RateLimitException (Python). Rate limit errors are now consistent across all SDK request paths.961ffba: feat(sdks): expose user-defined file metadata on sandbox.files
Adds a metadata option to file uploads (write / writeFiles / write_files) and surfaces persisted metadata on every EntryInfo / WriteInfo returned by getInfo, list, rename, and write responses. On upload, metadata is sent as X-Metadata-<key>: <value> request headers; envd persists the values as extended attributes in the user.e2b. xattr namespace and returns them on subsequent filesystem reads (including user.e2b.* xattrs set out-of-band). Keys are sent as HTTP header names and are lowercased by the sandbox; metadata is validated client-side (keys must be valid HTTP header tokens, values must be printable US-ASCII) and invalid input raises InvalidArgumentError / InvalidArgumentException. The same metadata map is applied to every file in a multi-file upload. Requires envd 0.6.2 or later.
da85b1e: Add an includeEntry/include_entry option to filesystem directory watching. When enabled, each FilesystemEvent carries the affected entry's EntryInfo (best-effort; left unset for events where the path no longer exists, such as remove/rename-away). Requires envd 0.6.3 or later; watching with this option against an older sandbox raises a template error.
7dc861f: fix: align behavior between the JS and Python SDKs
Python SDK:
commands.send_stdin and CommandHandle.send_stdin now accept bytes in addition to str, and the handle's send_stdin / close_stdin now accept a request_timeout.git.reset now accepts a typed GitResetMode and its validation error matches the JS SDK wording/ordering. GitResetMode is now exported.sandbox_url is now propagated through get_api_params.Template.from_image() now raises when only one of username / password is provided.get_info() no longer carries the envd access token on the returned SandboxInfo (the _envd_access_token field was unused), matching the JS SDK which strips it from getInfo.get_metrics() now raises TemplateException (was SandboxException) with the same message as the JS SDK when the sandbox is too old.JS SDK:
Sandbox.getInfo() now includes sandboxDomain, matching the Python SDK's single get_info. getFullInfo is deprecated and now just wraps getInfo (it no longer returns the envd access token).Sandbox.getMetrics() now returns [] in debug mode, matching the Python SDK. The debug short-circuit for getMetrics / kill is implemented on both the instance and static methods, so it applies consistently whether called as Sandbox.kill(sandboxId) or sandbox.kill().Template.fromImage() now requires both username and password when registry credentials are provided.Template.getBuildStatus() now defaults logsOffset to 0.requestTimeoutMs: 0 now explicitly disables the request timeout.getMetrics() now throws TemplateError (was SandboxError) when the sandbox is too old to support metrics.4e16cff: Fix proxy not being applied to volume content requests. Volume.create/Volume.connect now store the proxy on the returned instance, so instanc
proxy not being applied to volume content requests. Volume.create/Volume.connect now store the proxy on the returned instance, so instance methods (list, readFile, writeFile, makeDir, getInfo, updateMetadata, remove, …) route through it without having to pass proxy on every call. A per-call proxy still takes precedence.d86368a: Align the sync and async Python SDK implementations: consistent parameter ordering (_create, Commands._start), matching docstrings, keyword a
d86368a: Align the sync and async Python SDK implementations: consistent parameter ordering (_create, Commands._start), matching docstrings, keyword arguments in Filesystem.write, and a consistent bare Exception for the internal "Body of the request is None" guard (matching the volume client).
Sandbox.pause() / AsyncSandbox.pause() (and beta_pause) now return a bool — True if the sandbox got paused, False if it was already paused — matching the JS SDK. Previously the instance method returned None and the class-method form returned the sandbox ID.
08012ee: feat: add sendStdin/send_stdin and closeStdin/close_stdin to CommandHandle
You can now send and close stdin directly on a background command handle instead of going through sandbox.commands with the command's PID.
7296b2c: fix(python-sdk): stop sending E2b-Sandbox-Id/E2b-Sandbox-Port headers on the control-plane connect request
Sandbox.connect was attaching the envd data-plane headers (E2b-Sandbox-Id, E2b-Sandbox-Port) to the POST /sandboxes/{id}/connect API call. These headers belong only on data-plane (filesystem/commands/pty) requests, matching the JS SDK behavior.
f2550fa: Mark the Python SDK as typed (PEP 561). Added py.typed markers to the e2b and e2b_connect packages so type checkers like mypy and Pyright honor the inline annotations on Sandbox, AsyncSandbox, and other public APIs instead of treating imports as Any.
6c04e31: Use thread-local sync HTTP transports to avoid sharing HTTP/2 connection state across Python threads.
e2b-dev/infra. Notable changes: SandboxMetrics gains memCache / mem_cache (cached memory in bytes), NodeStatus gains standby, TeamUser.email is now nullable and deprecated, and POST /v3/templates can now return 403.Template.fromDockerfile / Template.from_dockerfile now correctly handle multi-source COPY/ADD instructions. Previously, only the first source was kept and all intermediate sources were silently dropped; now each source is emitted as its own copy() call to the same destination.AuthenticationError / AuthenticationException with an example token (e.g. e2b_0000000000000000000000000000000000000000) when the key does not start with e2b_ followed by hex characters.18a10afa: chore(js): add max concurrency limits
wait helper (js-sdk), asSandboxTemplate/asHeadline/selectOption/basicDockerfile (cli), and format_execution_timeout_error (python-sdk). No public API changes.RateLimitError (JS) / RateLimitException (Python). Rate limit errors are now consistent across all SDK request paths.4b9cc04: Use the stable sandbox host for envd requests on supported E2B domains and give envd traffic a separate HTTP transport cache from API traffic
3786f34: Support structured network rules with per-host transforms
Sandbox.updateNetwork / update_network to replace a running sandbox's egress configuration (allow / deny lists, per-host rules, internet access toggle).signal: AbortSignal option to Template.build, Template.buildInBackground, Template.getBuildStatus, Template.exists, Template.aliasExists, Template.assignTags, Template.removeTags, and Template.getTags. When the signal is aborted, the underlying request (and, for Template.build, the status polling loop) is cancelled and the returned promise rejects with an AbortError.e2b-dev/infra. Notable changes: SandboxMetrics gains memCache / mem_cache (cached memory in bytes), NodeStatus gains standby, TeamUser.email is now nullable and deprecated, and POST /v3/templates can now return 403.Template.fromDockerfile / Template.from_dockerfile now correctly handle multi-source COPY/ADD instructions. Previously, only the first source was kept and all intermediate sources were silently dropped; now each source is emitted as its own copy() call to the same destination.AuthenticationError / AuthenticationException with an example token (e.g. e2b_0000000000000000000000000000000000000000) when the key does not start with e2b_ followed by hex characters.3786f34: Support structured network rules with per-host transforms
Sandbox.updateNetwork / update_network to replace a running sandbox's egress configuration (allow / deny lists, per-host rules, internet access toggle).2680c89: Remove Sandbox.betaCreate (JS) and Sandbox.beta_create (Python). These methods were a beta of the lifecycle configuration that has since shipped on Sandbox.create. Migrate by calling Sandbox.create with the lifecycle option:
// before
await Sandbox.betaCreate({ autoPause: true })
// after
await Sandbox.create({ lifecycle: { onTimeout: 'pause' } })
# before
Sandbox.beta_create(auto_pause=True)
# after
Sandbox.create(lifecycle={"on_timeout": "pause"})
a9bb287: fix(python-sdk): close gRPC streams on AsyncWatchHandle.stop() and AsyncCommandHandle.disconnect()
a9bb287: fix(python-sdk): close gRPC streams on AsyncWatchHandle.stop() and AsyncCommandHandle.disconnect()
stop() / disconnect() previously only cancelled the consumer task and left the underlying server-streaming RPC open (the await self._events.aclose() was commented out as a Python 3.8 workaround). On long-lived sandboxes this leaked file descriptors and eventually produced Code.internal: error creating watcher: too many open files. Python 3.8 is no longer supported (pyproject.toml pins ^3.10), so the workaround is removed and the streams are now closed explicitly.
8640378: Make http2 parameter optional
Deprecate auto_pause/autoPause; use lifecycle={"on_timeout": "pause"} instead. A DeprecationWarning (Python) / console.warn (JS) is now emitted when t…
auto_pause/autoPause; use lifecycle={"on_timeout": "pause"} instead. A DeprecationWarning (Python) / console.warn (JS) is now emitted when the flag is setauto_resume=true while the resolved on_timeout is "kill" now raises InvalidArgumentException (Python) / InvalidArgumentError (JS)2680c89: Remove Sandbox.betaCreate (JS) and Sandbox.beta_create (Python). These methods were a beta of the lifecycle configuration that has since shipped on Sandbox.create. Migrate by calling Sandbox.create with the lifecycle option:
// before
await Sandbox.betaCreate({ autoPause: true })
// after
await Sandbox.create({ lifecycle: { onTimeout: 'pause' } })
# before
Sandbox.beta_create(auto_pause=True)
# after
Sandbox.create(lifecycle={"on_timeout": "pause"})
### Minor Changes - e10958d: support for api h2
auto_pause/autoPause; use lifecycle={"on_timeout": "pause"} instead. A DeprecationWarning (Python) / console.warn (JS) is now emitted when the flag is setauto_resume=true while the resolved on_timeout is "kill" now raises InvalidArgumentException (Python) / InvalidArgumentError (JS)eaf452a: add optional name parameter to createSnapshot and return snapshot names
2ac5de2: Add signal: AbortSignal option to JS SDK methods to support cancelling in-flight requests. The signal can be passed to Sandbox.create, Sandbo
2ac5de2: Add signal: AbortSignal option to JS SDK methods to support cancelling in-flight requests. The signal can be passed to Sandbox.create, Sandbox.connect, sandbox.commands.run, sandbox.files.*, volume methods, and other request options. When the signal is aborted, the underlying fetch is aborted and the returned promise rejects with an AbortError.
SandboxPaginator.nextItems and SnapshotPaginator.nextItems now accept a SandboxApiOpts argument (including signal) — when provided, the per-call options override the connection options the paginator was constructed with for that single request.
Same change in the Python SDK: SandboxPaginator.next_items / SnapshotPaginator.next_items (sync and async) now accept **opts: ApiParams (e.g. api_key, domain, headers, request_timeout); when provided, the per-call options override the ones the paginator was constructed with.
e2b sandbox fork --count, JavaScript Sandbox.fork({ count }), and Python Sandbox.fork(count=...) reject a count outside 1–20 before the API call. Counts from 21 through 100 used to reach the API. Omitting count still leaves the field off the request so the API default applies.55e9e0e: resolve Dependabot security alerts: bump vulnerable transitive npm deps via pnpm overrides (postcss, vite, lodash, brace-expansion, picomatch…
2c995d4: added opt-in useOctetStream / use_octet_stream option to sandbox file write; default is now multipart/form-data regardless of envd version
useOctetStream / use_octet_stream option to sandbox file write; default is now multipart/form-data regardless of envd versioneaf452a: add optional name parameter to createSnapshot and return snapshot names
cf35f61: switch to application/octet-stream for file uploads
e2b sandbox fork command to fork a running sandbox into one or more new sandboxes6d7e72e: added volumes support to the SDKs
### Minor Changes - f7f1f29: Update SDK READMEs
e2b.toml in sandbox create, template delete and template publish/unpublish, and remove their --config and --path options. Templates must now be passed as an argument or picked interactively with -s. template migrate is unchanged and remains the only command that reads e2b.toml.e2b sandbox exec to the remote command instead of parsing them as CLI optionstar 7.5.22 and @bufbuild/protobuf 2.14.0 in the JS SDK, statuses 2.0.2, async-listen 3.1.0 and yup 1.7.1 in the CLI. No behavior change.e2b sandbox create --detach now prints only the sandbox ID to stdout; the dashboard inspect link and other informational output go to stderr so the ID is easily parseable (e.g. SBX=$(e2b sandbox create -d)).docs.e2b.dev instead of e2b.dev/docs. The docs site moved to its own subdomain and has no /docs path prefix there, so e2b.dev/docs serves a 308 to docs.e2b.dev/ and e2b.dev/docs/code-interpreting maps to docs.e2b.dev/code-interpreting. The UTM parameters are unchanged and survived the redirect, so this removes a redirect hop rather than fixing broken attribution.### Minor Changes - f7f1f29: Update SDK READMEs
e2b sandbox snapshot commands: create <sandboxID> (with optional --name) to create a snapshot from a sandbox, list [sandboxID] (with optional --name filter and --format json) to list snapshots, and delete <snapshotIDs...> to delete snapshots.Sandbox.list. The order option ('asc' / 'desc', default 'desc') sorts sandboxes by start time across the whole paginated dataset, and the query now supports startedAfter / started_after (inclusive lower bound on start time) and template (exact template ID or alias) filters, all applied server-side before pagination. The CLI e2b sandbox list command exposes these via --order, --started-after, and --template.7c8d298: adds network configs and lifecycle handling to info responses
48e9249: Bump @npmcli/package-json from ^5.2.1 to ^7.0.5, clearing the last npm warn deprecated glob@10.5.0 warning printed on every @e2b/cli install (@npmcli/package-json@5 pinned glob@10; 7.0.4 moved to glob@13). Together with the e2b glob bump, a fresh npm install @e2b/cli is now warning-free and drops from 183 to 145 packages.
@npmcli/package-json@7 requires Node ^20.17.0 || >=22.9.0, so the CLI's Node 22 floor moves from >=22 to >=22.9.0. Node 20 support is unchanged (>=20.18.1 <21). Only the PackageJson.load/create/update/save API used by e2b template init is touched, and it is unchanged across the bump.
fixMissing / fix_missing option to aptInstall / apt_install to support the --fix-missing flag in apt-get install710fae6: request_timeout applies to connect/pool/write
16c86d1: Support multiple asyncio loops
5417dd4: Rebuild the CLI so the bundled tar picks up 7.5.19+, fixing the node-tar denial-of-service vulnerabilities (the CLI bundles the SDK and its d…
tar picks up 7.5.19+, fixing the node-tar denial-of-service vulnerabilities (the CLI bundles the SDK and its dependencies into dist/index.js)Your coding agent can read these notes before it upgrades. Set up the MCP server →