@e2b/python-sdk@2.38.0
5020 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6acbeb39ee | [skip ci] Release new versions @e2b/python-sdk@2.38.0 e2b@2.38.3 | ||
|
|
b048369307 |
feat(python-sdk): move the envd HTTP API client onto pyqwest (#1623)
## What Tracked in [SDK-265](https://linear.app/e2b/issue/SDK-265) (part of the [SDK-268](https://linear.app/e2b/issue/SDK-268) stack). Stacked on #1603, at the top of the pyqwest stack (#1601 → #1602 → #1603 → this). Migrate the envd HTTP API client — sandbox file transfers (`files.read`/`write`), health checks — from httpx-native transports to pyqwest via the httpx adapter, and dedupe the transport plumbing that #1558 (envd RPC) and #1601 (REST) each carried a copy of. With this, all Python SDK traffic runs on pyqwest: REST control plane (#1601), envd RPC (#1558, connectrpc), envd HTTP API (this PR); the volume content client (#1602) and template build uploads (#1603) sit below this one in the stack. ## How **Shared plumbing** (first commit): `e2b.api` becomes the canonical home for the proxy narrowing (`proxy_to_config`, with stack-neutral error messages), the pool tuning, and the flavor `ConnectionRetryTransport` + a new `retrying_http_transport(proxy, read_timeout=None)` factory; `e2b.envd.client_sync/client_async` import them instead of defining their own (envd RPC behavior unchanged, pools stay separate — unification is SDK-291). **envd HTTP API** (second commit): - `get_envd_transport(config, for_streaming=False)` returns pyqwest-adapter transports cached per `(proxy, streaming)`; `get_envd_api(config, base_url, for_streaming=False)` builds the httpx client with sandbox headers + logging hooks. The per-thread (sync) / per-loop (async) client caching in `Filesystem`/`Commands`/`Pty`/`AsyncSandbox` is gone — one shared client per module, same rationale as the `ApiClient` simplification in #1601. - **Streamed downloads**: the streaming transport carries a 60s `read_timeout` — an idle bound that resets on every read, capping stalls without limiting total transfer time. It gets a dedicated pool because reqwest's read timer keeps ticking while a request body is sent and while waiting for the response head, so on the shared transport it would cut off uploads and slow unary responses. An explicit `request_timeout` becomes the whole-transfer deadline (adapter semantics) and is sent only when the caller set one; `stream_idle_timeout` stays honored on the async client via `wait_for` per read (so values above 60s work and `0` disables), and is documented as ignored on the sync client, which cannot interrupt a blocking read. Mirrors #1602's volume design. - **Uploads**: buffered uploads keep `request_timeout` as a whole-request deadline; streamed (file-like) uploads carry no client-side timeout and are bounded server-side (envd's idle read timeout) — both exactly the JS SDK's behavior (`getSignal` for buffered, no signal for streams). - **Multipart**: `files=` uploads go out as httpx's `MultipartStream`, which implements *both* `SyncByteStream` and `AsyncByteStream`. The pyqwest 0.7 adapter's sync content conversion matched `AsyncByteStream` first and raised `TypeError("unreachable")` from inside the body iterator, surfacing as a `WriteError` mid-request ("http2 error: stream error sent by user"). Fixed upstream in [pyqwest#196](https://github.com/curioswitch/pyqwest/pull/196), which matches the sync case first — so this PR carries no workaround (the stack requires **pyqwest 0.9**, set in #1601). The regression test stays, now covering the upstream fix. - The stream readers map the transport's idle timeout (builtin `TimeoutError` under pyqwest) to the documented `httpx.ReadTimeout`; `handle_envd_api_transport_exception`'s health-probe path keeps working because the adapter maps HTTP/2 stream resets to `httpx.RemoteProtocolError`. - **RPC logging**: the `LoggingInterceptor` docstring no longer promises its own removal. pyqwest does log requests ([pyqwest#197](https://github.com/curioswitch/pyqwest/pull/197)), but on process-wide `pyqwest`/`pyqwest.access` loggers that can't carry the per-sandbox `logger` and don't see streamed messages or the Connect error code of a stream that fails inside a `200 OK` — so the interceptor stays, with those loggers below it. [pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192), the middleware it referenced, was closed in favor of #197. - **Transports**: rebased onto #1603 on pyqwest 0.9, so the envd HTTP API transports are the stock `PyqwestTransport`/`AsyncPyqwestTransport` (the SDK's adapter subclasses are gone as of #1601 — 0.9 strips the `Host` header and maps timeouts itself) with `follow_redirects=False` and, for the streaming pool, the transport-wide `read_timeout`. ## Testing - Unit: envd transport keying (streaming vs regular vs REST pools), `get_envd_api` wiring (headers, transports), multipart regression through a local server, stream-reader timeout mapping + per-read idle bound (`tests/test_file_stream_reader.py`), rewritten client-lifecycle tests (shared across threads). 236 unit tests green; lint + typecheck green. - Integration against production sandboxes: full `files` suites sync+async (123 tests — these caught the multipart bug), `commands` + `pty` suites both flavors (57 tests). All green. ## Usage example No API changes: ```python sbx = Sandbox.create() sbx.files.write("hello.txt", "hi") # multipart/octet-stream over pyqwest with sbx.files.read("hello.txt", format="stream") as stream: for chunk in stream: # stalls bounded by 60s idle read timeout ... ``` Only visible behavior shift: on the **sync** client, `files.read(..., format="stream", stream_idle_timeout=...)` is now a documented no-op (the transport-wide 60s idle bound applies); the async client honors it as before. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b3a7c9f44a |
feat(python-sdk): move template build-context uploads onto pyqwest (#1603)
## What Stacked on #1602 (which is stacked on #1601). Migrates the **template build-context uploads** (streaming the build archive to S3 presigned URLs in `build_api.upload_file`) onto [pyqwest](https://github.com/curioswitch/pyqwest) via its httpx-compatible transport adapter. Originally deferred from #1601 because S3 presigned URLs reject chunked transfer encoding and Content-Length framing through reqwest was unverified. Verified at the wire level (raw-socket capture server): httpx's Content-Length — derived from the spooled archive (sync) or set explicitly on the async-iterator body (async) — is forwarded by the adapter and reqwest keeps Content-Length framing for streamed bodies, no chunked fallback. > [!NOTE] > Rebased onto #1601, which locks **pyqwest 0.9.0**. Two knock-on changes here: the upload client uses the stock `PyqwestTransport`/`AsyncPyqwestTransport` (0.9.0's adapter subsumes what the SDK's transport subclasses did, so #1601 deleted them), and it builds its proxy from `proxy_to_config(...)` following #1601's rename. ## How - `e2b/template_sync/build_api.py` / `template_async/build_api.py`: `upload_file` uses a one-off pyqwest transport instead of the generated client's httpx transport. - **Redirects stay with the httpx client.** pyqwest 0.9.0 makes reqwest's internal redirect following configurable, so it's turned off on the upload transport: otherwise reqwest would replay the entire archive body against a new location without httpx knowing. The httpx client inherits the API client's `follow_redirects` (off), matching the httpx transport this replaced — so an unexpected hop surfaces as a failed upload rather than a silent re-upload. - `verify_ssl=False` on the generated client is no longer honored for uploads (pyqwest has no insecure-TLS option), and `http2=False` is gone (S3 negotiates HTTP/1.1 via ALPN anyway). - The 1-hour upload timeout now bounds the entire upload rather than each socket write — arguably the intended meaning for that endpoint. ## Testing - `tests/{sync,async}/*/test_upload_file.py` (the #1243 regression tests — Content-Length present and equal to the body, no chunked encoding) pass through pyqwest; the capture handlers now compare header names case-insensitively since hyper lowercases them where httpcore title-cased. - New in both mirrors: `test_upload_file_leaves_redirects_to_httpx` — a 307 on the upload URL surfaces as `FileUploadException` and the capture server sees exactly one PUT, guarding against reqwest silently following the hop and replaying the archive. - Lint (`ruff`), typecheck (`ty`), upload-file suites: green (10/10). ## Usage example No API changes — template builds upload their context exactly as before: ```python from e2b import Template template = Template().from_image("ubuntu:22.04").copy("data/", "/data") Template.build(template, alias="my-template") # archive upload now goes through pyqwest ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
458c2c4362 |
feat(python-sdk): move the volume content client onto pyqwest (#1602)
## What Stacked on #1601. Migrates the **volume content client** (`Volume`/`AsyncVolume` file operations) onto [pyqwest](https://github.com/curioswitch/pyqwest) via its httpx-compatible transport adapter — the same stock httpx transport adapter + connection-retry stack the REST API client uses after #1601. Originally deferred from #1601 because `Volume.read_file(format="stream")` relied on httpx's per-read `read` timeout as an *idle* timeout, which the adapter can't express per request (it converts the httpx timeout dict into a whole-request deadline, and the sync adapter doesn't bound body reads at all). Unblocked by pyqwest's transport-constructor `read_timeout`, which maps to reqwest's `ClientBuilder::read_timeout` — verified behaviorally (local slow-chunk server, sync + async) to be a true per-read idle timeout: it resets after each successful read, covers body reads, and a healthy stream longer than the timeout completes untouched. > [!NOTE] > Rebased onto #1601, which maps `httpx.Proxy` onto pyqwest's `Proxy` object and locks pyqwest 0.9.0. Following that: this PR builds its transport from `proxy_to_config(...)` instead of `proxy_to_url(...)`, uses the stock `PyqwestTransport`/`AsyncPyqwestTransport` (0.9.0's adapter drops the redundant `Host` header and maps pyqwest's timeouts and connection, network, and protocol failures to their httpx counterparts, so the SDK's transport subclasses are gone), and turns reqwest's internal redirects off so httpx owns them, as the generated volume client expects. ## How - `e2b/volume/client_sync/__init__.py` / `client_async/__init__.py` move to the same stock adapter + connection-retry stack as the API client. Caches become process-global, keyed by (proxy, streaming) — previously one pool per thread (sync) / per event loop (async). - Streamed downloads go through a **dedicated streaming transport** with `read_timeout=60s`. It can't live on the shared transport: reqwest's read timer keeps running while a request body is sent and while waiting for the response head (verified empirically — a 2.4 s upload against a 0.5 s `read_timeout` dies mid-send), so a shared `read_timeout` would cut off `write_file` uploads and slow unary responses longer than the idle bound. Uploads and unary calls stay on a transport without it, bounded by their whole-request deadlines as before. - The 60 s default matches the JS SDK exactly: JS bounds stream start by `requestTimeoutMs` (60 s default) and idle gaps by `streamIdleTimeoutMs ?? requestTimeoutMs`; the Python streaming transport's `read_timeout` bounds the response head and each idle gap at 60 s, resetting on every chunk, wire-only (a slow consumer doesn't trip it — verified). - `AsyncVolume.read_file` keeps honoring an explicit `stream_idle_timeout` **per call**, the same way JS honors `streamIdleTimeoutMs` and #1558 bounds stream setup: `asyncio.wait_for` around each read (response head and every chunk). Explicit values run on the *regular* transport, so a value above the 60 s transport bound isn't capped by it and `0` disables idle bounding entirely, restoring the previous contract. The sync client keeps the parameter but **ignores** it — it has no way to interrupt a blocking read into the Rust transport, so its bound must live in the transport. - Streamed reads are sent without a per-request timeout so the adapter imposes no whole-request deadline on long downloads; an explicitly passed `request_timeout` becomes the total-transfer deadline. - A stalled read surfaces as `httpx.ReadTimeout`, keeping the established contract: the 0.9.0 adapter maps its own timeouts, and the async flavor remaps the per-read `stream_idle_timeout` (an `asyncio.wait_for` expiry) to match. - Proxy narrowing follows #1601: `str`, `httpx.URL`, and reducible `httpx.Proxy` values work; inexpressible extras raise `InvalidArgumentException`. ## Testing - `tests/test_volume_client.py` rewritten: process-global transport caching (shared across threads and event loops), streaming vs regular transport separation, plus end-to-end streamed reads through `Volume.read_file`/`AsyncVolume.read_file` against a local chunked server — a healthy stream longer than the idle timeout completes (proves the timeout resets per read), a mid-body stall raises `httpx.ReadTimeout`, a slow response head on a *non-streamed* read is not cut off by the idle bound, and a slow response head on a streamed read is (JS handshake-timeout parity). Async `stream_idle_timeout`: an explicit value aborts a stall, a value above the transport bound isn't capped by it, and `0` disables idle bounding. - Volume content integration tests couldn't run end-to-end (the test team's key gets `403: use of volumes is not enabled`); the mock-transport volume content tests and the local-server stream tests cover that path. - Lint (`ruff`), typecheck (`ty`), unit suite: green. ## Usage example No API changes for the common path: ```python volume = Volume.connect(volume_id, token=token) stream = volume.read_file("big.bin", format="stream") # stalls bounded by the for chunk in stream: # transport-wide idle read ... # timeout (httpx.ReadTimeout) volume.read_file("big.bin", format="stream", stream_idle_timeout=5) # sync: accepted, ignored async_volume = await AsyncVolume.connect(volume_id, token=token) stream = await async_volume.read_file( "big.bin", format="stream", stream_idle_timeout=5 # async: honored per read, ) # 0 disables idle bounding ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a874ced97a |
feat(python-sdk): move the REST API client onto pyqwest's httpx transport adapter (#1601)
## What Migrate all httpx REST API client traffic in the Python SDK — the E2B control plane (sandbox lifecycle, listing, templates, volumes control plane) — to [pyqwest](https://github.com/curioswitch/pyqwest) (Rust reqwest/hyper), using its httpx-compatible transport adapter (`pyqwest.httpx.PyqwestTransport` / `AsyncPyqwestTransport`). The generated openapi client and `ApiClient`/`AsyncApiClient` keep their httpx surface — logging event hooks, per-request timeouts, headers, and redirects behave as before — only the transport underneath is swapped. envd RPC already runs on pyqwest via connectrpc (#1558). This PR touches only the control-plane client; the rest of the stack builds on it: #1623 (envd HTTP API client), #1602 (volume content client), #1603 (template uploads). Requires **pyqwest 0.9** — pinned in `pyproject.toml` (`>=0.9.0,<0.10`) with `uv.lock` refreshed. 0.8 brought the `Proxy` object ([pyqwest#194](https://github.com/curioswitch/pyqwest/pull/194)) and request loggers ([pyqwest#197](https://github.com/curioswitch/pyqwest/pull/197)); 0.9 ([release notes](https://github.com/curioswitch/pyqwest/discussions/214)) folds the two adapter workarounds this PR used to carry into the adapter itself and makes redirect handling configurable, so the SDK no longer subclasses the adapter at all. ## How - `e2b/api/client_sync/__init__.py` / `client_async/__init__.py`: `get_transport` now returns a pyqwest-backed httpx transport — a `SyncHTTPTransport`/`HTTPTransport` (`tls_include_system_certs=True`, proxy, pool tuning mapped from `E2B_KEEPALIVE_EXPIRY`/`E2B_MAX_KEEPALIVE_CONNECTIONS`), wrapped in a `ConnectionRetryTransport` for connect-only retries honoring `E2B_CONNECTION_RETRIES`, wrapped in the stock `PyqwestTransport`/`AsyncPyqwestTransport` httpx adapter. - pyqwest transports are thread-safe and loop-independent (I/O runs on a Rust tokio runtime), so the caches are process-global keyed by proxy — previously one pool per thread (sync) / per event loop (async). - **`ApiClient` sheds its threading machinery**: the `transport_factory`/`async_transport_factory` plumbing, the thread-local `httpx.Client` cache, and the per-loop `WeakKeyDictionary` of `AsyncClient`s are gone. A single lazily-created httpx client (the generated base behavior, the same shape the volume client already uses) serves all threads and event loops; `httpx.Client` is documented thread-safe and nothing below it is loop-bound. Closing that client can't tear down the shared pool — the adapter transports don't override `close()`/`aclose()`. - **Host header** (upstream in 0.9): sending the `Host` header httpx auto-adds on an HTTP/2 connection makes the E2B API edge reset the stream with `PROTOCOL_ERROR` (reproduced with plain pyqwest against `api.e2b.app`); hyper derives `Host`/`:authority` from the URL. The adapter now skips a `host` header matching the URL, so the SDK-side strip is gone — and unlike that strip, a genuinely custom `Host` override is still forwarded. - **Timeout exceptions** (upstream in 0.9): pyqwest raises the builtin `TimeoutError`; the adapter maps it to `httpx.ReadTimeout` both while awaiting the response head and while reading the body, preserving the `httpx.TimeoutException` contract for callers. Connection, network, and protocol failures likewise arrive as `httpx.ConnectError`/`ConnectTimeout`, `httpx.ReadError`/`WriteError`, and `httpx.RemoteProtocolError` instead of leaking pyqwest/builtin types. - **Redirects**: the pyqwest transports are built with `follow_redirects=False` (0.9 made it configurable; reqwest's default is to follow). Otherwise redirects are followed inside the transport, hiding 3xx responses from httpx and leaving `response.history` empty — even though the generated clients ask for no redirect following. httpx owns them again, as with the transports this replaced. - **Proxy**: `proxy=` accepts a URL string, `httpx.URL`, or an `httpx.Proxy` — including its credentials (sent as `Proxy-Authorization`) and any headers configured for the proxy, via pyqwest's `Proxy` object. `proxy_to_config` normalizes all three into a `ProxyConfig` tuple that both keys the transport cache and builds the `pyqwest.Proxy`, so the same proxy URL with different credentials or headers gets its own pool. A per-proxy `ssl_context` has no counterpart and raises `InvalidArgumentException` rather than being silently dropped. (`ProxyConfig` is a `NamedTuple`, not a frozen dataclass: `tests/test_env_var_parsing.py` reloads `e2b.api`, and a dataclass `__eq__` compares class identity, so keys built before and after a reload would silently stop matching.) - **`ProxyTypes` is ours now**: the public type of the `proxy` option (already exported from `e2b`) used to be imported at runtime from httpx's private `_types` module in eleven modules. It is defined there as `Union[str, URL, Proxy]` — exactly the three forms the SDK's two narrowers accept — so it's spelled out once in `e2b.connection_config` and imported from there. Same public name, same type to a type checker, no private-module dependency, and a place for a pyqwest proxy type to land as the remaining transports move off httpx. `e2b.envd.client_shared.proxy_to_url` took a bare `object` while `e2b.api.proxy_to_config` took `Optional[ProxyTypes]`; both now say the same thing. `isinstance` narrowing stays rather than duck-typing `.url`/`.auth` — httpx is a required dependency here (the generated REST client *is* an httpx client, and envd file transfers use httpx directly), so probing attributes would trade a clear `InvalidArgumentException` on a mistyped argument for no dependency savings. - **Request logs**: pyqwest logs one line per request on the `pyqwest.access` logger and lifecycle records on `pyqwest`, both at `DEBUG` — the transport-level diagnostics httpcore used to provide, now that httpcore is out of the path. Noted on `get_transport`; the SDK's own `logger` option is unchanged and sits above it on the httpx client. - **HTTP/2**: negotiated via ALPN for TLS connections (reqwest default), equivalent to the `http2=True` transports this replaces. ## What stays behind (handled by the stacked PRs) - **envd HTTP API client** (file transfers, health checks): #1623, which also dedupes the transport plumbing this PR and #1558 each carry a copy of (the proxy narrowing, pool tuning, retry transport — envd keeps byte-identical duplicates until then). - **Volume content client**: its streaming download relies on httpx's per-read `read` timeout as an *idle* timeout, which the adapter can't express per request — #1602. - **Template build context upload**: one-off httpx client PUTing to S3 presigned URLs — #1603. ## Timeout semantics note `request_timeout` was previously httpx's per-phase timeout (connect/read/write each bounded separately, so a slow multi-phase request could exceed it in total). Through the adapter it becomes an overall deadline per API call (async: headers + body; sync: up to response headers). For the SDK's REST calls — all unary with small JSON bodies — this is a tightening, arguably closer to what `request_timeout` promises. ## Testing - `tests/test_api_client_transport.py` rewritten for the new semantics: global per-proxy transport caching, a single httpx client shared across threads/loops (including 32-way concurrent request tests against a local server), timeout → `httpx.ReadTimeout` mapping for both the response head and a stalled body (slow/stalling local server), redirects surfacing to httpx (302 returned as-is, `response.history` populated when the caller opts in), the connection-only retry policy, `proxy_to_config` conversion, and sync+async round-trips through a real local HTTP server exercising pyqwest end to end. The two host-header unit tests are gone with the subclasses they tested — that behavior is the adapter's now. - Two tests cover the pyqwest proxy/logging surface: an echo server standing in for a proxy asserts that the absolute-form request target, `Proxy-Authorization`, and the extra proxy header actually arrive, and the `pyqwest.access` record is asserted for an API call. - On pyqwest 0.9.0 from PyPI: `uv sync --locked`, unit suite (`tests/*.py`, 238 passed), `ruff check`, `ty check` — all green. - Integration against the production API (real key) was run on 0.8.0: `tests/sync/api_sync`, `tests/async/api_async`, create/kill/timeout/connect — all green. (These initially failed with `RemoteProtocolError: StreamReset` until the host header stopped being forwarded, so they genuinely exercise the new stack; that fix now comes from the adapter.) ## Usage example No API changes for the common path: ```python from e2b import Sandbox sbx = Sandbox.create() # control-plane calls now go through pyqwest Sandbox.list() sbx.kill() ``` Proxy handling — URL strings and `httpx.Proxy` objects work, credentials and proxy headers included: ```python Sandbox.create(proxy="http://user:pass@localhost:8030") # ok (unchanged) Sandbox.create(proxy=httpx.Proxy("http://localhost:8030", auth=("user", "pass"))) # sent as Proxy-Authorization Sandbox.create(proxy=httpx.Proxy("http://localhost:8030", headers={"X-Auth": "t"})) # sent to the proxy Sandbox.create(proxy=httpx.Proxy("https://localhost:8030", ssl_context=ctx)) # raises InvalidArgumentException ``` `ProxyTypes` — already exported from `e2b` — is now defined by the SDK rather than re-exported from `httpx._types`, with the same three members: ```python from e2b import ProxyTypes # Union[str, httpx.URL, httpx.Proxy] ``` Transport-level HTTP logs, replacing the httpcore records this migration removes: ```python import logging logging.basicConfig() logging.getLogger("pyqwest.access").setLevel(logging.DEBUG) Sandbox.create() # DEBUG pyqwest.access - HTTP Request: POST https://api.e2b.app/sandboxes "HTTP/2 201 Created" ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cab27aa6fa |
fix(sdk): clean up sandbox when MCP gateway startup fails (#1548)
## Problem Fixes #1498. `Sandbox.create` allocates a remote sandbox before starting `mcp-gateway`. If gateway startup fails, creation throws before the sandbox object is returned. As a result, the caller has no sandbox ID to clean up, and the orphaned sandbox continues consuming resources until it times out. This state transition exists in synchronous Python, asynchronous Python, and JavaScript/TypeScript. ## Changes - Add a rollback boundary around MCP gateway startup in all three SDK implementations: on failure, best-effort kill the newly allocated sandbox, then re-raise. - Surface gateway startup failure as `SandboxError` (JS) / `SandboxException` (Python) with a `Failed to start MCP gateway: <stderr>` message. Previously the intended message was unreachable dead code — foreground `commands.run` already throws on non-zero exit — so callers got a bare `CommandExitError`/`CommandExitException`. - In async Python, re-raise `asyncio.CancelledError` from the best-effort `kill()` so caller cancellation (e.g. `asyncio.timeout`) is honored; only ordinary cleanup failures are suppressed and never mask the original error. - Add integration coverage for synchronous Python, asynchronous Python, and TypeScript. The tests pin the sandbox to the base template (which has no `mcp-gateway` binary) so gateway startup genuinely fails after allocation. - Add a patch changeset for `e2b` and `@e2b/python-sdk`. ## Usage Behavior No API changes. A failed creation no longer leaves a sandbox behind, and the error is now descriptive: ```ts try { const sandbox = await Sandbox.create({ mcp: { ... } }) } catch (err) { // err is SandboxError: "Failed to start MCP gateway: <stderr>" // the allocated sandbox has already been killed — no orphan is left running } ``` ## Validation All three integration tests verified against real infra: creation rejects with the documented error and no sandbox remains. ## Notes Supersedes #1547 by @hxaxd (squash-merged into this branch to preserve attribution). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: 苏紫辰 <155808914+hxaxd@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e6111419b5 | [skip ci] Release new versions e2b@2.38.2 | ||
|
|
d5a382ed67 |
chore(js-sdk): bump undici to ^7.29.0 and optional undici8 to 8.10.0 (#1645)
Bumps both undici dependencies in the js-sdk past the 2026-07-24 security advisories: the required `undici` from `^7.28.0` to `^7.29.0`, and the optional `undici8` (`npm:undici@…`) from 8.8.0 to 8.10.0. Both releases patch one High ([GHSA-4cwx-7wf7-3272](https://github.com/nodejs/undici/security/advisories/GHSA-4cwx-7wf7-3272), cache-control parsing / cross-user disclosure) and four Medium advisories, clearing the open Dependabot alerts for undici; 8.10.0 additionally fixes HTTP/2 request settling, refused-stream retries and GOAWAY handling, which we exercise because every dispatcher the SDK builds sets `allowH2: true`. A root `pnpm.overrides` entry (`undici@>=7.0.0 <7.29.0`) is included because miniflare pins undici at exactly 7.28.0, which would otherwise keep a vulnerable copy in the lockfile; with it, the lockfile carries only 7.29.0 and 8.10.0. No code change was needed and there is no user-facing API change — 7.29.0 still requires Node `>=20.18.1` and 8.10.0 still requires `>=22.19.0`, matching the `UNDICI_8_MIN_NODE` gate in `packages/js-sdk/src/undici.ts`, so `getUndiciPackageCandidates()` picks the same package on the same Node versions. `format`, `lint` and `typecheck` pass, `tests/undici.test.ts` is 9/9, and `test:cf` was run to confirm miniflare still boots on the overridden undici. The remaining vitest projects need `E2B_API_KEY`, which isn't available locally, so they're left to CI. A patch changeset for `e2b` is included. Linear: [SDK-317](https://linear.app/e2b/issue/SDK-317/js-sdk-bump-optional-undici8-dependency-to-8100) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6cce3fde9d |
ci: pin GitHub Actions to full commit SHAs (#1646)
Every external action in `.github/` is now referenced by a 40-character commit SHA with the release tag as a trailing comment, so a compromised or retagged upstream release cannot silently change what runs in CI — this covers 78 `uses:` refs across 15 files, leaving in-repo `./.github/...` composite-action and reusable-workflow refs as-is since they are not a supply-chain surface. Each SHA was resolved from the tag the workflow already floated on and re-verified against the GitHub API, so the change is behaviour-preserving; all 15 files were also re-checked as valid YAML. Two pins are worth a reviewer's attention: - **`pnpm/action-setup` is pinned to v4.3.0, not v4.4.0.** Upstream's `v4.4.0` tag points at the same commit as `v5.0.0`, while the floating `v4` tag we were on still resolves to v4.3.0 — pinning to v4.4.0 would have silently jumped a major. - **`actions/checkout@v3` and `actions/create-github-app-token@v1` are pinned at their latest v3/v1 SHAs rather than bumped** to v4/v2, keeping this PR to pinning alone; bumping those majors is a good follow-up. A second commit unifies `dorny/paths-filter`, which was the one action already pinned (at v3.0.3 in the Dependabot changeset workflow) and would otherwise have left the repo carrying two SHAs for the same action; its comment justified the pin as being "rather than floating on `v3`", which no longer distinguishes it now that everything is pinned, so it is rewritten to keep only the still-relevant `pull_request_target` warning. One gap this PR does not close: there is no `.github/dependabot.yml` in the repo, so nothing will keep these SHAs current and they will drift away from upstream security fixes — adding a `github-actions` ecosystem entry (which understands SHA pins with version comments and bumps both) is worth doing separately. No SDK or CLI package is touched, so no changeset is needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2d2823c94a | [skip ci] Release new versions @e2b/python-sdk@2.37.1 e2b@2.38.1 | ||
|
|
88f41f3927 |
fix(python-sdk): port current JS stripAnsi regex to strip_ansi_escape_codes (#1545)
## Summary The Python SDK's `strip_ansi_escape_codes` (used to clean template build log messages) still used the old ansi-regex pattern, while the JS SDK's `stripAnsi` was rewritten in #895. This ports the current JS regex to Python so both SDKs clean logs identically: OSC sequences (hyperlinks, window titles) are matched non-greedily up to the first string terminator — including content spanning newlines — and CSI sequences are stripped without requiring a terminator. Following review feedback, both implementations now also strip the remaining ECMA-48 string controls — DCS (Sixel, tmux passthrough), SOS, PM, and APC — through their string terminator, so control payloads don't leak into cleaned logs. This goes beyond upstream `chalk/ansi-regex`, click, and Rich, none of which fully strip DCS payloads, and restores what the old Python pattern handled. Also mirrors the Python test suite into the JS SDK (which previously had no `stripAnsi` tests) — 20 identical cases per side — and verified byte-for-byte identical output between the two implementations on all of them. Includes a patch changeset for `e2b` and `@e2b/python-sdk`. ## Example Log messages that previously leaked OSC or DCS sequences into template build output are now cleaned: ```python from e2b.template.utils import strip_ansi_escape_codes strip_ansi_escape_codes("\x1b]8;;https://e2b.dev\x07E2B\x1b]8;;\x07") # "E2B" strip_ansi_escape_codes("\x1b]0;title\nstill title\x07done") # "done" strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") # "RED" strip_ansi_escape_codes("\x1bPq#0;2;0;0;0~~@@\x1b\\image") # "image" (Sixel DCS) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
26ee42c10a |
chore: upgrade pnpm to 10.34.5 and delay fresh releases by 3 days (#1644)
Bumps pnpm 9.15.5 → 10.34.5 in all three places it is pinned (`.tool-versions`, the root `packageManager` field, and `codegen.Dockerfile` — pnpm 10 self-manages from `packageManager`, so a mismatched Docker pin would make it re-download itself on every `make generate`) and sets `minimumReleaseAge: 4320` in `pnpm-workspace.yaml`, so a freshly published version is not resolved until it is 3 days old; CI is unaffected because every workflow installs with `--frozen-lockfile` and nothing installs a just-published package. Two pnpm 10 breaking changes needed handling: dependency lifecycle scripts no longer run by default, so `esbuild` and `workerd` are allowlisted via `pnpm.onlyBuiltDependencies` for binary resolution while `bufferutil`, `msw`, and `utf-8-validate` are explicitly declined via `pnpm.ignoredBuiltDependencies` (which also keeps the "Ignored build scripts" warning off every install); and pnpm 10 stopped public-hoisting `*prettier*`/`*eslint*`, which broke `pnpm run format` in both JS packages with `prettier: command not found` — prettier was never declared anywhere and only resolved because pnpm 9 hoisted it out of `json-schema-to-typescript`, so it is now a root devDependency alongside `oxlint`, resolved to the 3.6.2 already in the lockfile for zero formatting churn. `engines.pnpm` moves to `>=10.16.0 <11` so, with `engine-strict`, pnpm 9 fails with an actionable "install the required pnpm version globally" message instead of silently installing. Verified with a clean `node_modules` + `--frozen-lockfile` install and green `lint`, `typecheck`, `format`, and both JS builds, plus a from-scratch re-resolve of the whole tree to confirm `minimumReleaseAgeStrict` (which silently defaults to `true` once the age is set explicitly) does not trap any current range; enforcement was checked empirically — with the setting, `wrangler@^4` resolves to 4.118.0 (6d old) rather than 4.119.0 (1d old). The lockfile diff is limited to the prettier entry plus pnpm 10's importer-section reordering and new `libc:` fields, with no dependency version drift. No changeset: nothing in a published package changed. A follow-up to pnpm 11 is deliberately out of scope — it removes both build-script fields in favor of `allowBuilds`, restricts `.npmrc` to auth/registry settings, and replaces the npm-delegating `pnpm publish`, which the release flow depends on. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
998e560a1a |
fix(python-sdk): relax wcmatch constraint to >=10.1,<12 (#1638)
|
||
|
|
86f7b8e2f8 |
fix(js-sdk): export the Git argument and status types (#1642)
Carries the change from #1635 (by @karpovantonme) into `main` as a single squash commit — #1635 was retargeted at `fix/export-git-argument-types`, merged there, and this PR promotes that branch. `Git.reset()`, `Git.restore()` and `Git.status()` are public, but the types naming their arguments and results were not reachable from the package entry point. `src/index.ts` re-exported fifteen `Git*` types and omitted `GitResetMode`, `GitResetOpts` and `GitRestoreOpts`. `GitStatusLabel` was worse off — `src/sandbox/git/index.ts` re-exported `GitBranches`, `GitConfigScope`, `GitFileStatus` and `GitStatus` from `./utils` but not `GitStatusLabel`, so it was unreachable from anywhere in the package, even though it is the type of `GitFileStatus.status`. The practical effect: you could call the methods, but you could not name what you pass them, so you could not write a typed wrapper. ```ts // before — all four fail import type { GitResetMode, GitResetOpts, GitRestoreOpts, GitStatusLabel, } from 'e2b' // the workaround people end up with type ResetMode = Parameters<Git['reset']>[0] extends { mode?: infer M } ? M : never ``` ```ts // after import { Sandbox } from 'e2b' import type { GitResetMode, GitResetOpts, GitStatusLabel } from 'e2b' async function hardResetTo(sbx: Sandbox, repo: string, target: string) { const mode: GitResetMode = 'hard' const opts: GitResetOpts = { mode, target, cwd: repo } return sbx.git.reset(opts) } function isBlocking(status: GitStatusLabel) { return status === 'conflict' || status === 'deleted' } ``` ## On SDK parity Python already exports `GitResetMode` (`e2b.GitResetMode`), so this brings JS up to it. The other three have no Python counterpart by design: the sync and async implementations take keyword arguments rather than option objects, so there is nothing shaped like `GitResetOpts`/`GitRestoreOpts`, and `GitFileStatus.status` is typed as a plain `str` there, so there is no `GitStatusLabel` either. Nothing to mirror on the Python side. ## Notes - Type-only re-exports, no runtime change. Changeset included (`e2b`: patch). - No test: a missing re-export is invisible to `tsc --noEmit` because `packages/js-sdk/tsconfig.json` includes only `src`, so an `import type … from '../src'` test passes either way. A declaration-reading test was dropped from #1635 during review. ## Not touched The same gap exists for a few non-Git types — `FilesystemListOpts`, `WatchOpts`, `PtyCreateOpts` and `PtyConnectOpts` are exported from their own modules but not from `src/index.ts`. Scope kept to the Git surface, as in #1635. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Anton Karpov <30812217+karpovantonme@users.noreply.github.com> Co-authored-by: Anton Karpov <karpovantonme@gmail.com> |
||
|
|
9c555a12aa |
ci: add a changeset to Dependabot pull requests automatically (#1639)
Dependabot bumps of a direct production dependency of `e2b`, `@e2b/cli` or `@e2b/python-sdk` need a changeset to reach users, and they kept merging without one (#1461, #1443). This workflow commits a `patch` changeset naming every released package the bump touches, and stays out of the way otherwise — dev-only bumps, transitive-only lockfile bumps, and pull requests that already carry a hand-written changeset are all skipped. The push uses the version-bumper App token rather than `GITHUB_TOKEN`, whose commits do not start workflow runs, so the required checks would never report on the new head commit and the pull request would be unmergeable. For #1461 it would have committed `.changeset/dependabot-1461.md`: ```md --- 'e2b': patch --- Update the `undici` dependency to 7.28.0. ``` The `changes` job now skips the Dependabot metadata lookup once a changeset is on the branch, so the workflow's own commit never sends `fetch-metadata` looking for metadata on a pull request that is no longer all-Dependabot commits, and `dorny/paths-filter` is SHA-pinned as the one third-party action this trigger reaches. Verified by running the commit step against a scratch repository with the exact expression outputs for single-package, grouped multi-package and Python-only bumps, then parsing each result with changesets' own `@changesets/parse`. Two things to watch on the first live run: the org-level `verification/cla-signed` check has to accept the App's commit, and adding a commit stops Dependabot auto-rebasing the branch (`@dependabot rebase` still works, and the workflow rewrites the changeset afterwards). 🤖 Generated with [Claude Code](https://claude.com/claude-code) SDK-311 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7a1fe4528c | [skip ci] Release new versions @e2b/python-sdk@2.37.0 e2b@2.38.0 | ||
|
|
2821fb0b69 |
feat(sdk): route volume content to BYOC cluster domain (#1634)
When a team is connected to a custom (BYOC) cluster, the volume API now returns that cluster's domain in the create and get responses. The JS and Python (sync + async) SDKs use this domain as the destination for volume content requests instead of the default api.<E2B_DOMAIN> host, falling back to the configured domain when none is returned. The domain field is read defensively from the response until spec/infra-ref is bumped to the infra commit that adds it and `make codegen` regenerates the typed schema. Claude-Session: https://claude.ai/code/session_01212WCmNz1prPKrjhTv2PDj --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Matt Brockman <matt.brockman@e2b.dev> |
||
|
|
9ef3f1dbbe | [skip ci] Release new versions @e2b/cli@2.16.1 @e2b/python-sdk@2.36.0 e2b@2.37.0 | ||
|
|
1ebe925ee0 |
fix(js-sdk): detect web platform objects by shape, not by class (#1618)
SDK-299
## Symptom
Two shapes of failure, one cause.
Every control-plane call crashing in an app that embeds the SDK next to
a server shim:
```
TypeError: Failed to parse URL from [object Request]
at fetch (…/undici/index.js:157:10)
at wrapped (…/e2b/src/undici.ts:126)
```
…and, quietly, uploads that arrive at the sandbox containing the eight
bytes `[object Blob]` instead of the file.
## Root cause
`value instanceof Blob` does not answer *"is this a Blob"*, it answers
*"was this minted by the `Blob` class this module happens to see"*. In a
Node process those are different questions: libraries replace the web
globals exactly the way they replace `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 of all of them — and values also cross realms (`node:vm`,
`worker_threads`). `src/undici.ts` already late-binds the global `fetch`
for this reason; the brand checks never got the same treatment.
It is reachable with a **single** shim install, no exotic dependency
duplication:
1. `openapi-fetch` captures `Request: CustomRequest =
globalThis.Request` when the client is created (`dist/index.mjs:11`) and
mints every request from it,
2. `EnvdApiClient` is built once in the `Sandbox` constructor and stored
(`src/sandbox/index.ts:201`), so it outlives anything that swaps the
global afterwards,
3. from then on the SDK checks each request against a class that did not
mint it. Which copy "wins" the global is import-order dependent, so the
crash appears and disappears with unrelated dependency changes.
Verified locally, frame for frame: real `undici`/`undici8` throw
`TypeError: Failed to parse URL from [object Request]` for **any**
`Request` they did not mint — including Node's native one — so the
destructure in `toUndiciRequestInput` is load-bearing and a missed brand
check is fatal rather than merely slower.
## The whole family
Every brand check on the data path had the same defect, and each one
failed differently:
| Site | Misfires on | User-visible effect |
| --- | --- | --- |
| `undici.ts` `toUndiciRequestInput` | foreign `Request` | **every API
call throws** `Failed to parse URL from [object Request]` |
| `api/inflight.ts` `limitConcurrency` | foreign `Request` | abort
signal ignored while the request waits for a slot |
| `utils.ts` `toBlob` | foreign `Blob` | upload body is the text
`"[object Blob]"` |
| `utils.ts` `toBlob` | foreign `ReadableStream` | upload body is the
text `"[object ReadableStream]"` |
| `utils.ts` `toUploadBody` (gzip) | foreign `ReadableStream` |
`pipeThrough(new CompressionStream())` never settles — the upload hangs
|
| `utils.ts` `toUploadBody` | foreign `ReadableStream` | file buffered
into memory instead of streamed (OOM on large files) |
| `filesystem/index.ts` `hasStreamableData` | foreign `ReadableStream` |
same, plus the multipart path is chosen for a stream |
| `volume/index.ts` `readFile` | foreign `Blob`/`ArrayBuffer` |
**returns an empty file** |
| `undici.ts` `toUndiciRequestInput` (body) | foreign `Request`'s stream
body | body sent as the text `"[object ReadableStream]"` |
The `Blob`/stream rows are silent data corruption, confirmed against
real undici:
```js
await new Response(foreignBlob).text() // → "[object Blob]"
await new Response(foreignStream).text() // → "[object ReadableStream]"
```
## Fix
New internal `src/is.ts` asks what a value *is*: `instanceof` stays the
fast path, then it falls back to the members and `Symbol.toStringTag`
the platform guarantees (`isRequestLike`, `isBlobLike`,
`isReadableStreamLike`, `isArrayBufferLike`). Nothing is added to the
public surface.
Detection alone is not enough where the SDK hands data back to the
platform — the platform brand-checks too, and a detected-but-not-adopted
foreign stream would be stringified instead of buffered, i.e. worse than
before. So the conversions adopt what they detect:
- `toBlob` copies a foreign `Blob`'s bytes (`new Blob([await
data.arrayBuffer()], { type: data.type })`) and pumps a foreign stream
through a native one via its reader;
- the adoption itself is not class-dependent, which took two rounds to
get right (see *Adoption* below);
- `toUploadBody` returns `{ body, streamed }` instead of leaving
`filesystem`/`volume` to re-derive "did it stream?" with another brand
check on the result — only that function knows the decision it made.
### What used to break
```ts
import { serve } from '@hono/node-server' // installs its own globalThis.Request
import { Sandbox } from 'e2b'
const sandbox = await Sandbox.create()
await sandbox.files.write('/tmp/a.txt', 'hi') // TypeError: Failed to parse URL from [object Request]
```
```ts
import { ReadableStream } from 'web-streams-polyfill' // not the native class
// Used to upload the literal text "[object ReadableStream]"; with gzip it hung.
await sandbox.files.write('/tmp/big.bin', bigPolyfillStream, { gzip: true })
```
### Adoption
The platform accepts exactly two kinds of stream body: **its own
class**, and **any async iterable** (verified against `undici@8`/Node —
everything else is stringified). Async iterability is the half that
survives a replaced global, since a native stream stays async-iterable
even when `globalThis.ReadableStream` is a polyfill. Hence two helpers,
each named for the contract it satisfies:
- `toDispatchableStream` — for request bodies: passes through the
platform's own class *or* an async iterable, adopts the rest. Adopting
on `!(stream instanceof ReadableStream)` alone would have been
class-dependent in the same way as the bug: with a polyfilled global, a
perfectly good native stream fails the check and gets re-wrapped into a
polyfill instance the platform likes *less*.
- `toNativeStream` — for `pipeThrough(new CompressionStream(…))`, the
stricter consumer: it insists on its own class, so async iterability is
not enough there and every foreign stream is adopted.
Everything that isn't already a stream reaches the gzip path through
`toBlob`, whose result is always a native `Blob`, so the gzip path needs
no blob branch of its own. That in turn means nothing ever calls
`stream()` on a `Blob` the SDK didn't make, so `isBlobLike` only
requires `arrayBuffer` beyond the tag — one less requirement is one less
implementation whose upload would silently be the text `"[object
Blob]"`.
The same reasoning applies one level down: a `Request` from another
fetch implementation exposes *that* implementation's stream as its
`body`, so accepting the Request without adopting its body would only
move the stringification, not remove it.
## Tests
`tests/foreignPlatformObjects.ts` provides the fixtures: `ForeignBlob`
and `foreignReadableStream` are separate implementations rather than
subclasses (a subclass still passes `instanceof`, so it would prove
nothing), and `foreignRequestClasses()` returns two sibling subclasses
of the native `Request` — instances of one are fully functional Requests
that the other disowns, which is what the shims actually produce.
- `tests/is.test.ts` — the four predicates, including the negatives that
keep them honest (a `URL`, a string, an `IncomingMessage`-shaped `{ url,
method, headers }`).
- `tests/utils.test.ts` — content round-trips for
`toBlob`/`toUploadBody` across native/foreign `Blob`s and streams, plus
a gzip round-trip through `DecompressionStream` for all four input
kinds.
- `tests/undici.test.ts` — a disowned `Request` reaches undici
destructured as `(url, init)`; and, on Node, the same request goes
through the **real** undici `fetch` (via `MockAgent`, so no network) and
is matched on method, path and headers.
- `tests/api/inflight.test.ts` — an already-aborted disowned `Request`
rejects with `AbortError` without consuming a slot.
Each adoption rule has a test that fails without it: dropping the
async-iterable clause fails the "does not re-wrap a native stream when
the global class was replaced" case, using `toDispatchableStream` in the
gzip path fails the async-iterable gzip case, and dropping the body
adoption fails with `expected '[object ReadableStream]' to be 'hello'`.
Red/green: with `src/` reverted, the three Request tests fail (the
undici one with the production error, `ERR_INVALID_URL { input: '[object
Request]' }`) and the data-path tests fail with `expected '[object
Blob]' to be 'hello'` / `expected '[object ReadableStream]' to be
'hello'`.
Verification run: unit + `connectionConfig` (129), `tests/sandbox/files`
+ `tests/volume` against prod (92 passed, real streamed/gzipped
uploads), all four changed files green on Node, Bun, Deno and workerd,
plus `tsc`, `lint`, `prettier` and `build`.
## Out of scope
`network.rules instanceof Map` (`sandboxApi.ts`) and the `instanceof
Error` checks are left alone: nothing replaces `globalThis.Map`, and the
errors are ours. Python needs no counterpart — its REST stack takes
bytes/iterables and has no equivalent brand checks.
Supersedes #1610 (@himself65), which fixed the `Request` half of this
and diagnosed the crash; the reproduction there is what led to auditing
the rest.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
2df7651ee6 |
test(sdk): run firewall transform tests against an httpbin sidecar sandbox (#1631)
Follow-up to #1632, which added the template this depends on. Now rebased onto `main`, so this is just the test change. ## Problem The firewall transform tests asserted header injection by curling `httpbin.e2b.team`, an externally hosted service the suite had to keep alive. ## Fix Starts a sidecar sandbox from the `httpbin` template instead: the rule is keyed on the sidecar's `getHost(8080)` and the assertion reads the injected header back from `/headers`, in the JS, sync Python, and async Python suites. The sidecar's ready command has already passed by the time `create` resolves, so the server is serving and no readiness polling is needed. The template name lives in one fixture per SDK — `httpbinTemplate` in `tests/template.ts` and the `httpbin_template` fixture in `conftest.py`. Also drops two comments merged in #1632 that claimed the tests spawn `e2b/httpbin`. The bare alias is what resolves, same as `base` — the team slug only appears in the display name. ⚠️ Do not merge before **Build and push prepared templates** has been dispatched with `template: httpbin` — the tests resolve the template by name and fail until it exists on the E2B team. Verified against production: all three tests pass with the injected header reflected by the sidecar, spawning the template by its bare alias with a key that owns it — the same situation as CI. SDK-304 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b54e8d10df |
ci: add an httpbin template and a dropdown to pick which one to build (#1632)
## Problem The firewall transform tests assert header injection by curling `httpbin.e2b.team`, an externally hosted service the suite has to keep alive. Transforms are applied by the egress proxy on the way *out* of a sandbox, so the target has to be publicly reachable — which rules out a CI service container, but not another sandbox. ## Change Adds a `httpbin` template (`templates/httpbin`): go-httpbin on `debian:bookworm-slim`, SHA256-pinned against the release `checksums.txt` the way `templates/base` pins its Node install. Neither official image can serve as an E2B base — `ghcr.io/mccutchen/go-httpbin` is distroless (no shell, and `dockerfileParser.ts:79` rejects multi-stage so the binary can't be copied out), and `kennethreitz/httpbin` is Ubuntu 18.04 whose build fails on E2B's `fuse3` install (both verified by building). `templates.yml` gains a `template` choice input (`all` / `base` / `httpbin`) rather than a second near-identical workflow file. `all` is the default so a plain dispatch behaves as before, and the DockerHub image job is skipped for `httpbin`, which has no image counterpart. The template stays **private to the E2B team**, like `base` — publishing would only expose it to other projects, and the tests that spawn it use our API key anyway. The alias is passed unprefixed because the server namespaces it with the team slug, so the name the tests resolve is **`e2b/httpbin`**; only `base` predates namespacing and stays bare. Merge this, then dispatch **Build and push prepared templates** with `template: httpbin` — #1631 stacks on top and resolves the template as `e2b/httpbin`. <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub> SDK-304 Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4a2571d321 |
test(js-sdk): wait for workers.dev propagation in the CF deploy suite (#1592)
## Problem The `cloudflare-deploy` CI job intermittently fails with `non-JSON response (404): <!DOCTYPE html>...` ([example run](https://github.com/e2b-dev/E2B/actions/runs/30009788154/job/89214641280)). The truncated HTML boilerplate is easy to mistake for a Cloudflare captcha/challenge page, but it's the standard Cloudflare **404** page: each `wrangler deploy --temporary` lands on a brand-new account subdomain (`e2b-js-sdk-smoke.<random>.workers.dev`), and Cloudflare serves "nothing is here yet" until the route propagates to the edge. The in-test retry window (initial attempt + 10 retries × 3s ≈ 35s) wasn't always enough. ## Fix - `setup.mts`: after the deploy, poll the worker URL until the worker itself answers (405 to GET — the worker is POST-only), with a 240s deadline. Tests only start once the route is live. Only the propagation 404 and thrown fetch errors (transient DNS/connect) keep the poll waiting — any other status (403 challenge, 500 from a broken worker, ...) is a real failure and fails the setup immediately, with the error page's `<title>` in the message. - `run.test.ts`: retry only on the propagation 404 / `fetch failed`, so other Cloudflare error pages propagate on the first attempt; include the page `<title>` in the `non-JSON response` error, since the truncated body is boilerplate shared by every Cloudflare error page. Test-only change, no changeset. ## Verification Ran `pnpm test:cf:deploy` against real Cloudflare (both revisions): ``` Deployed: https://e2b-js-sdk-smoke.quick-bike.workers.dev Worker route not live yet (404), waiting... Worker route not live yet (404), waiting... Worker is live. Test Files 1 passed (1) Tests 1 passed (1) ``` The fresh subdomain served 404 for ~6s post-deploy — exactly the failure mode from CI — then the suite passed on the first test attempt. `pnpm run format`, `lint`, and `typecheck` pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
86934c779c |
ci(js-sdk): make the Cloudflare deploy test leg advisory (#1622)
## Problem The `cloudflare-deploy` leg deploys to a brand-new Cloudflare preview account on every run, so it inherits that account's propagation and read-after-write races — the fresh `workers.dev` subdomain 404s until the route reaches the edge, and the subdomain API can 404 the script it just accepted (`This Worker does not exist on your account [code: 10007]`). That fails ~1 run in 8 (6 of ~46 runs since 2026-07-24) without saying anything about the SDK, and the job gates both the required `SDK Tests Status` check and the release workflow's `publish` step — both of today's release runs were blocked by it ([30473411814](https://github.com/e2b-dev/E2B/actions/runs/30473411814), [30474175682](https://github.com/e2b-dev/E2B/actions/runs/30474175682)). ## Fix `continue-on-error` on that matrix leg only, so it still runs and still reports on every PR but no longer blocks merges or releases. The signal survives: a genuine bundle regression (e.g. the #1579 Workers startup crash) is rejected at upload deterministically, not intermittently. Follow-ups to make the leg reliably green again: #1592 (propagation poll, still open) plus a retry around `wrangler deploy --temporary` for the 10007 race. CI-only change — no changeset, no user-facing surface. Closes SDK-301 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
45d26792f1 |
chore(deps-dev): bump datamodel-code-generator from 0.34.0 to 0.64.0 in /packages/python-sdk in the uv group across 1 directory (#1621)
> [!NOTE]
> Manual follow-up commit on top of Dependabot's bump (addressing review
feedback): the codegen image pin was out of sync, so this PR also bumps
it and carries the regenerated output.
### Manual changes on top of the bump
- `codegen.Dockerfile` bumped from `datamodel-code-generator==0.34.0` to
`0.64.0`. `pyproject.toml`'s `codegen` group and the Dockerfile must
stay in sync (the comment above the group says so) — CI's `Generated
files` check regenerates from the image, so leaving the image at
`0.34.0` would make `make init` produce output CI rejects.
- `packages/python-sdk/e2b/sandbox/mcp.py` regenerated with `0.64.0`.
Two output changes:
- builtin generics (`list[str]`, `dict[str, Any]` instead of
`List`/`Dict`), fine on the SDK's `>=3.10` floor;
- `additionalProperties: false` in `spec/mcp-server.json` is now honored
as PEP 728 `closed=True` (`0.34.0` silently dropped it), and `TypedDict`
is imported from `typing_extensions` accordingly.
- `typing-extensions>=4.1.0` → `>=4.10.0`. `closed=True` is evaluated at
class-creation time, i.e. on `import e2b`, and 4.10.0 is the first
release whose `TypedDict` accepts the keyword (4.9.0 raises `TypeError:
_TypedDictMeta.__new__() got an unexpected keyword argument 'closed'`).
- Changeset added (`patch` for `@e2b/python-sdk`), since the regenerated
file and the dependency floor ship to users.
Nothing changes for callers at runtime — `McpServer` is still a plain
dict at the call site:
```python
from e2b import Sandbox
sbx = Sandbox.create(mcp={"duckduckgo": {}, "brave": {"braveApiKey": "..."}})
```
The `closed` types are also inert for type checkers in practice, because
the public `McpServer` is `Union[BaseMcpServer, GitHubMcpServer]` and
the second arm is a `Dict[str, ...]`. Verified: `pyright` and `mypy`
both clean against the snippet above, `ruff`/`ty`/`pnpm typecheck`
clean, 283 offline Python unit tests pass, and regenerating with the
full pinned toolchain (`python:3.10` + `black==26.3.1` + the other
Dockerfile pins) reproduces the committed file byte-for-byte.
---
Bumps the uv group with 1 update in the /packages/python-sdk directory:
[datamodel-code-generator](https://github.com/koxudaxi/datamodel-code-generator).
Updates `datamodel-code-generator` from 0.34.0 to 0.64.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/releases">datamodel-code-generator's
releases</a>.</em></p>
<blockquote>
<h2>0.64.0</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python < 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List["Node"]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/koxudaxi/datamodel-code-generator/blob/main/CHANGELOG.md">datamodel-code-generator's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/koxudaxi/datamodel-code-generator/releases/tag/0.64.0">0.64.0</a>
- 2026-06-14</h2>
<h2>Breaking Changes</h2>
<h3>Code Generation Changes</h3>
<ul>
<li>Self-referencing fields are now quoted with
<code>--disable-future-imports</code> - When
<code>--disable-future-imports</code> is set (no <code>from __future__
import annotations</code> and no native PEP 649 deferred evaluation on
Python < 3.14), self-referencing and forward-referencing field
annotations in regular <code>BaseModel</code> classes are now emitted as
quoted forward references instead of bare names. Previously such
annotations were left unquoted, producing invalid code that raised
<code>NameError</code> (Ruff F821) at class-evaluation time. Output for
the common case (with <code>from __future__ import annotations</code> or
Python 3.14 native deferred annotations) is unchanged. Users who
snapshot/golden-file generated output for the
<code>--disable-future-imports</code> configuration with
self-referencing models will see the annotation change from unquoted to
quoted, e.g. <code>children: Optional[List[Node]]</code> →
<code>children: Optional[List["Node"]]</code>. (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
</ul>
<h2>What's Changed</h2>
<ul>
<li>Update CHANGELOG for 0.63.0 by <a
href="https://github.com/dcg-generated-docs"><code>@dcg-generated-docs</code></a>[bot]
in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3345">koxudaxi/datamodel-code-generator#3345</a></li>
<li>Deduplicate module content builder by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3346">koxudaxi/datamodel-code-generator#3346</a></li>
<li>Deduplicate import reference helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3348">koxudaxi/datamodel-code-generator#3348</a></li>
<li>Refactor jsonschema root model registration by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3352">koxudaxi/datamodel-code-generator#3352</a></li>
<li>Refactor XML Schema literal helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3349">koxudaxi/datamodel-code-generator#3349</a></li>
<li>Move builtin formatter helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3351">koxudaxi/datamodel-code-generator#3351</a></li>
<li>Deduplicate Pydantic v2 config helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3350">koxudaxi/datamodel-code-generator#3350</a></li>
<li>Deduplicate DataType type hint rendering by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3354">koxudaxi/datamodel-code-generator#3354</a></li>
<li>Fix <code>constr()</code> for string fields carrying
minItems/maxItems by <a
href="https://github.com/DarkaMaul"><code>@DarkaMaul</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3353">koxudaxi/datamodel-code-generator#3353</a></li>
<li>Cover non-finite import idempotence by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3367">koxudaxi/datamodel-code-generator#3367</a></li>
<li>Deduplicate input text detection by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3357">koxudaxi/datamodel-code-generator#3357</a></li>
<li>Remove stale protobuf coverage pragma by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3358">koxudaxi/datamodel-code-generator#3358</a></li>
<li>Cover explicit null OpenAPI media schemas by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3360">koxudaxi/datamodel-code-generator#3360</a></li>
<li>Simplify Python version feature checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3361">koxudaxi/datamodel-code-generator#3361</a></li>
<li>Speed up CI checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3378">koxudaxi/datamodel-code-generator#3378</a></li>
<li>Add maintainer link to docs footer and README by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3379">koxudaxi/datamodel-code-generator#3379</a></li>
<li>Use builtin formatter in CI by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3380">koxudaxi/datamodel-code-generator#3380</a></li>
<li>Split coverage by OS by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3381">koxudaxi/datamodel-code-generator#3381</a></li>
<li>Simplify import removal cleanup by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3362">koxudaxi/datamodel-code-generator#3362</a></li>
<li>Pin deprecation warning stacklevel by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3363">koxudaxi/datamodel-code-generator#3363</a></li>
<li>Pin public module exports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3364">koxudaxi/datamodel-code-generator#3364</a></li>
<li>Cover to_hashable branch cases by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3366">koxudaxi/datamodel-code-generator#3366</a></li>
<li>Cover stable toposort behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3369">koxudaxi/datamodel-code-generator#3369</a></li>
<li>Extract registry render helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3371">koxudaxi/datamodel-code-generator#3371</a></li>
<li>Fix minItems for arrays of URI strings by <a
href="https://github.com/sjh9714"><code>@sjh9714</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3377">koxudaxi/datamodel-code-generator#3377</a></li>
<li>Deduplicate config value validators by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3372">koxudaxi/datamodel-code-generator#3372</a></li>
<li>Cover CLI option metadata helpers by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3374">koxudaxi/datamodel-code-generator#3374</a></li>
<li>Cover Pydantic v2 version fallback by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3368">koxudaxi/datamodel-code-generator#3368</a></li>
<li>Fix nullable JSON Schema const enums by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3355">koxudaxi/datamodel-code-generator#3355</a></li>
<li>Pin patchable generation seams by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3365">koxudaxi/datamodel-code-generator#3365</a></li>
<li>Cover utility helper behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3375">koxudaxi/datamodel-code-generator#3375</a></li>
<li>Cover DefaultPutDict behavior by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3376">koxudaxi/datamodel-code-generator#3376</a></li>
<li>Cover validator config normalization by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3373">koxudaxi/datamodel-code-generator#3373</a></li>
<li>Avoid expensive runtime type checks by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3382">koxudaxi/datamodel-code-generator#3382</a></li>
<li>Avoid eager builtin formatter import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3383">koxudaxi/datamodel-code-generator#3383</a></li>
<li>Avoid eager TOML parser import by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3384">koxudaxi/datamodel-code-generator#3384</a></li>
<li>Stabilize msgspec payload tests by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3385">koxudaxi/datamodel-code-generator#3385</a></li>
<li>Avoid eager input parser imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3386">koxudaxi/datamodel-code-generator#3386</a></li>
<li>Avoid eager parser model imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3388">koxudaxi/datamodel-code-generator#3388</a></li>
<li>Avoid eager AsyncAPI converter imports by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3389">koxudaxi/datamodel-code-generator#3389</a></li>
<li>Dispose parser on parse errors by <a
href="https://github.com/koxudaxi"><code>@koxudaxi</code></a> in <a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/pull/3390">koxudaxi/datamodel-code-generator#3390</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/53a25ab8ddb132ac68a2795247fc855b8f445d84"><code>53a25ab</code></a>
Fast path schema output (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3410">#3410</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ee2087f32e6100f5c3642e7ea8506aa38e9df26c"><code>ee2087f</code></a>
Skip discriminator import scan (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3411">#3411</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bdf5ddfc27f94a06ba8d289759193bb09daadd34"><code>bdf5ddf</code></a>
fix: quote self-referencing fields when --disable-future-imports is set
(<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3387">#3387</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/ad4ec877fa6708baebdaaf820171d24bfe5bf0cb"><code>ad4ec87</code></a>
Cache payload validation strategies (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3409">#3409</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b191d52a0a1d83edeac9553119f70b2f5c131126"><code>b191d52</code></a>
Shard Python tests (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3408">#3408</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/29dd6d74c95dd7799d51f2c707db24862809eb23"><code>29dd6d7</code></a>
Cache parsed sources (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3407">#3407</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/93e2fe3cf5774d5e4d2083fac365e5bcbf0a647a"><code>93e2fe3</code></a>
Defer generation refresh (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3406">#3406</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/bb01d9c628f9077cc5dd72320ae60a18abd5b790"><code>bb01d9c</code></a>
Lazy root format exports (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3405">#3405</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/48237ed8c3af3bb58b5e6b274925ebb646412d3a"><code>48237ed</code></a>
Fast path JSON schemas (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3404">#3404</a>)</li>
<li><a
href="https://github.com/koxudaxi/datamodel-code-generator/commit/b21d106c88ac22f137cd4562389ad95a50c2e912"><code>b21d106</code></a>
Slot generation facts (<a
href="https://redirect.github.com/koxudaxi/datamodel-code-generator/issues/3403">#3403</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/koxudaxi/datamodel-code-generator/compare/0.34.0...0.64.0">compare
view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions
You can disable automated security fix PRs for this repo from the
[Security Alerts page](https://github.com/e2b-dev/E2B/network/alerts).
</details>
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
6733f36755 |
fix(sdk): align Python Fedora/Alpine image defaults with JS (#1625)
Python `from_fedora_image` defaulted to `fedora:42` (end-of-life) and `from_alpine_image` to `alpine:3.22`, while JS already pinned `fedora:44`/`alpine:3.24` — the same call produced a different base image per SDK. Aligns Python; also fixes the JS type docs, which still named the old defaults. ```python Template().from_fedora_image() # fedora:44 (was fedora:42) Template().from_alpine_image() # alpine:3.24 (was alpine:3.22) ``` Follow-up to #1612; both defaults are still unreleased. |
||
|
|
1504fbc843 |
SDK: fromFedoraImage/fromAlpineImage/fromArchImage helpers (#1612)
## What Adds the missing non-Debian base-image convenience helpers to **both SDKs**, mirroring the existing `fromUbuntuImage`/`fromDebianImage`/`fromPythonImage`/`fromNodeImage`/`fromBunImage`: - **JS/TS** (`packages/js-sdk`): `fromFedoraImage(variant?)`, `fromAlpineImage(variant?)`, `fromArchImage(variant?)` + unit tests - **Python** (`packages/python-sdk`): `from_fedora_image(variant)`, `from_alpine_image(variant)`, `from_arch_image(variant)` + sync/async unit tests ## Why This is the **customer-facing half** of infra **#3381** (distro-aware template provisioning). The engine now builds + boots Ubuntu/Debian/Fedora/RHEL-family/Arch/Alpine on real KVM; before this PR the SDK exposed distro helpers for the Debian family only, so Fedora/Alpine/Arch were reachable only via the generic `fromImage()`. These give them first-class parity. ## Verification (honest) - **New helper unit tests pass locally** — JS `fromDistroImages.test.ts` → 6/6 green (`vitest`, no auth). Python `test_from_distro_images.py` (sync + async) committed. - **Full integration suite**: requires E2B API keys — fails locally with `AuthenticationError` **identically on `main`** (215/187/29), i.e. **zero regression** from this change; CI runs it with secrets. - Lint scoped to the touched files. ## Not in this PR The public **docs** still state *"only Debian-based images … Alpine/RedHat not supported"* — but that text lives in **`e2b-dev/docs`**, not this monorepo, so it's a **separate docs PR** (being opened against `e2b-dev/docs`). Flagging so this + that land together. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
9e3e52b4fb |
Add --user/--cwd/--env terminal flags to sandbox create & connect (#1501)
Exposes `--user`, `--cwd`, and repeatable `--env KEY=VALUE` flags on `e2b sandbox create` (and the deprecated `spawn` alias) and `e2b sandbox connect`, forwarding them to the underlying PTY session so the connected terminal starts as the given user, in the given working directory, and with the given environment variables. The SDKs already supported these PTY options — this just wires them through the CLI. The `--env` arg parser is extracted into a shared `src/utils/env.ts` and reused across `create`, `connect`, and `exec`. Added unit tests for the parser and CLI tests covering the new flags; a changeset is included for `@e2b/cli`. ## Usage ```bash # Start the terminal as root, in /app, with custom env vars e2b sandbox create base --user root --cwd /app --env FOO=bar --env TOKEN=abc123 # Same flags when attaching to an already-running sandbox e2b sandbox connect <sandboxID> --user root --cwd /app --env FOO=bar ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
05b7a792ff |
fix(ci): depend on the SDK via workspace:^ so releases tag the version bump (#1619)
Closes
[SDK-298](https://linear.app/e2b/issue/SDK-298/release-tags-point-at-the-commit-before-the-version-bump).
Replaces #1615, which moved the tags after the fact instead of removing
the reason they were misplaced.
## The bug
Every published release tag pointed at the commit *preceding* its own
version bump:
```console
$ git show '@e2b/python-sdk@2.35.0:packages/python-sdk/pyproject.toml' | head -3
[project]
name = "e2b"
version = "2.34.0" # ← tagged 2.35.0
```
Anything that builds from a git tag rather than a registry got the
previous release: distro packagers, `pip install git+…@tag`, any bisect
over a release regression. `python3Packages.e2b` in nixpkgs shipped
1.5.0 as 1.5.1 from June 2025.
## Root cause: a dependency cycle
`changeset publish` tags whatever commit it publishes from, so the fix
is to commit the version bump first. That was impossible:
```
tag must point at → release commit
release commit must contain → pnpm-lock.yaml
pnpm-lock.yaml contains → integrity hash of a tarball this release uploads
```
`packages/cli` depended on `e2b` by registry range, so `changeset
version` rewrote that range and the lockfile had to be re-resolved
against a tarball that did not exist yet. The lockfile could only be
refreshed *after* publishing, which forced the commit — and therefore
the tags — after it too.
## The fix
`packages/cli`: `"e2b": "^2.36.1"` → `"e2b": "workspace:^"`.
The lockfile now records `link:../js-sdk` and stops changing at release
time, so the release commit is complete before anything is uploaded:
| | before | after |
|---|---|---|
| 1 | `pnpm run version` | `pnpm run version` |
| 2 | publish **+ tag** ← wrong commit | **commit** (local) |
| 3 | refresh `pnpm-lock.yaml` (retry ≤6×) | publish **+ tag** ← right
commit |
| 4 | commit + push | push |
That deletes the lockfile-refresh step and its whole
registry-propagation retry loop (#1589), and `createGithubReleases:
true` keeps doing the tagging and GitHub releases — no custom tagging
code. Keeping the commit local also improves recovery: a publish that
uploads *nothing* leaves the branch untouched with the changesets
intact, so re-dispatching retries cleanly.
### Landing that commit is now mandatory, so the push is resilient
Once the tags point at a local commit, getting it onto the branch stops
being bookkeeping. `changesets/action` pushes each tag as soon as
`changeset publish` reports it (`runPublish` → `git.pushTag`), *before*
it propagates a non-zero exit — so three things changed:
- **The push is gated on the tags themselves** — `git tag --points-at
HEAD` — not on whether the publish step succeeded. The tags are the
thing that has to end up reachable, so they are the right thing to ask.
A partial failure (npm succeeds, then python-sdk's `postPublish` fails
on PyPI) used to skip the push and strand tags on a commit that reached
no branch while `main` kept the old versions.
I first wrote this as `!cancelled() && (success() ||
steps.release.outputs.published == 'true')`, which was wrong in both
directions: `success()` fires in exactly the case that must be skipped
(publish exits 0 having uploaded nothing → pushes a bump with no tags,
cementing a version that can never be published), and `published` is
left unset when the action *throws* after tagging (`core.setOutput` runs
only on a normal return from `runPublish`, but `git.pushTag` happens
inside it) — so it was skipped in the very case it existed for. The tag
gate also covers `@e2b/python-sdk`, which the npm-derived output never
did, since `privatePackages.tag` is on.
- **A partial publish is reported, not swallowed.** It still has to land
— otherwise the pushed tags hang off no branch — but the bump is then on
the branch with the changesets consumed, so re-dispatching will not
retry what failed. The step now names the tags that did land and points
out that `postPublish`'s PyPI upload was skipped (the root script is
`changeset publish && ... postPublish`, so a non-zero npm exit
short-circuits it).
- **A non-fast-forward is reconciled with a merge,** not a rebase (which
would orphan the tags) and not a hard failure. Hard-failing left an
already-published release needing manual git surgery, and a naive
re-dispatch would publish nothing (versions already on the registry),
tag nothing, and report **success** — quietly recreating SDK-298.
- **`git add -A` replaces `commit -am`,** which cannot stage new files.
`changeset version` writes each `CHANGELOG.md` fresh, so no release
commit has ever contained one:
```console
$ git show --stat
|
||
|
|
2c061eb8cf |
chore(cli): migrate dashboard links to project-era tab entrypoints (#1605)
## Summary Following the dashboard's teams→projects rename (e2b-dev/dashboard#521), this PR points the `--project` flag help at the dashboard's `?tab=general` entrypoint (General settings, where the project ID lives) instead of the old `?tab=team`, and adds a `@e2b/cli` patch changeset. It also rewrites the CLI README's headless-auth note to use `E2B_API_KEY` (the supported browserless path) instead of `E2B_ACCESS_TOKEN`, pointing at the dashboard's API Keys tab, and drops the now-redundant `E2B_ACCESS_TOKEN` vs `E2B_API_KEY` callout. The SDKs' `?tab=keys` and the CLI's `?tab=personal` links stay unchanged — those tabs remain valid entrypoints. ## Example ``` $ e2b template create --help -t, --project <project-id> specify the project ID that the operation will be associated with. You can find project ID in the project settings in the E2B dashboard (https://e2b.dev/dashboard?tab=general). ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ee0ad25117 |
docs(sdk): rename team to project in snapshot docstrings (#1562)
Renames team → project terminology in the JS and Python SDK snapshot docstrings: the `list_snapshots`/snapshot list `name` filter example now reads `"my-project/my-snapshot"`, and `SnapshotInfo.names` is documented as "including project slug and tag (e.g. project-slug/my-snapshot:v2)". Documentation-only — no exported names, runtime behavior, or wire protocol change; generated API clients and `spec/openapi.yml` are intentionally untouched until the backend exposes project-named endpoints. Includes a patch changeset for `e2b` and `@e2b/python-sdk`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cf8296cf89 | [skip ci] Release new versions | ||
|
|
48e9249270 |
fix(cli): bump @npmcli/package-json to ^7, clearing deprecated glob@10 (#1614)
Follow-up to #1613, which fixed the `glob@11` deprecation warning in `e2b` but left `@e2b/cli` warning via `@npmcli/package-json@5 → glob@10`. That PR proposed `@npmcli/package-json@7`, but `^7` alone isn't enough — 7.0.0–7.0.2 still depend on the equally deprecated `glob@^11`, and the move to `glob@13` only landed in **7.0.4**, so this pins `^7.0.5`. Since `@npmcli/package-json@7` requires Node `^20.17.0 || >=22.9.0`, the CLI's Node 22 floor moves from `>=22` to `>=22.9.0` — matching the dependency exactly rather than excluding anyone it still supports. Node 20 support is unchanged, since `^20.17.0` covers the existing `>=20.18.1 <21`. ## Before / after ```console $ npm install @e2b/cli # before npm warn deprecated glob@11.1.0: Old versions of glob are not supported... npm warn deprecated glob@10.5.0: Old versions of glob are not supported... added 183 packages in 4s $ npm install @e2b/cli # after (both tarballs packed locally) added 145 packages in 1s ``` No API change. `e2b template init` is the only consumer, and the `PackageJson.load`/`create`/`update`/`save` surface it uses is unchanged across the bump. ## Verification - Packed `e2b` + `@e2b/cli` and installed into a scratch project with `overrides` pointing `e2b` at the local tarball (the post-release state): zero deprecation warnings, `npm ls glob --all` reports only `glob@13.0.6`. - Ran `e2b template init -n my-tmpl -l typescript` from that packed install against a real host `package.json` — scripts added, pre-existing scripts preserved. - `packages/cli` suite: 102 passed / 1 skipped, including all 14 `template init` tests, which assert on the written `package.json` in both the `load` (existing file) and `create` (no file) branches. `template/create.test.ts` fails identically on a clean tree in this environment — it requires `E2B_API_KEY`. - `pnpm run format` / `lint` / `typecheck` clean. `@types/npmcli__package-json` stays at `^4.0.4`; v7 ships no types. - `.tool-versions` is untouched: the pinned `nodejs 22.18.0` already satisfies `>=22.9.0`, so CI (which derives `node-version` from that file) needs no change. Closes SDK-297. Follow-up to #1613 (SDK-296). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>e2b@2.36.1 @e2b/cli@2.16.0 |
||
|
|
178e267ba2 |
fix(js-sdk): bump deprecated glob@^11 to ^13 (#1613)
Closes #1611. `e2b` declared `"glob": "^11.1.0"`, and glob 11 is deprecated on npm, so **every** `npm install` of any project that depends on `e2b` — directly or transitively — printed a deprecation warning. Downstream packages can't silence it themselves: npm `overrides` and `npm-shrinkwrap.json` only apply to the top-level project being installed, not to a transitive dependency's own range. It can only be fixed here. Thanks @clayboby for the report and the verification work. ## Before / after ```console $ npm install e2b@2.36.0 # before npm warn deprecated glob@11.1.0: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. added 37 packages in 1s $ npm install e2b # after (this branch, packed locally) added 26 packages in 1s ``` No API change — this is a dependency bump. The 37 → 26 package drop comes from glob 13 moving its CLI (and `jackspeak`/`@isaacs/cliui`/`string-width`/… ) out to a separate `glob-bin` package. ## Why ^13 is safe glob 12 and 13 only made **CLI-only** breaking changes, per [glob's changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md): - **v12** — "Remove the unsafe `--shell` option." - **v13** — "Move the CLI program out to a separate package, `glob-bin`." The SDK's only use of glob is `getAllFilesInPath` in the template build path (`src/template/utils.ts`, loaded via `dynamicImport('glob')`), which touches the named async export `glob(pattern, opts)`, the options `ignore` / `withFileTypes` / `dot` / `cwd`, and `Path#isDirectory()` / `#fullpath()` / `#relative()`. All unchanged in 13. glob 13.0.6's `engines` (`18 || 20 || >=22`) satisfy the SDK's (`>=20.18.1 <21 || >=22`), and it's still dual CJS/ESM, so both build outputs resolve it. ## Also in this PR: `"types": ["node"]` in the js-sdk tsconfig glob 13 pulls `minipass@^7.1.3`, which removed the `/// <reference types="node" />` that TypeScript 7's native `tsc` was (accidentally) relying on to see Node globals — it doesn't auto-include `node_modules/@types`. Without this, the bump fails `tsc --noEmit` with ~25 `TS2591 Cannot find name 'process'/'Buffer'` errors. Requesting `node` explicitly is the right fix and makes the typecheck independent of a transitive dependency's d.ts. ## Verification - `tsc --noEmit` clean for js-sdk and cli; `pnpm run lint` / `format` clean; `tsdown` build clean and `glob` still emitted as an external `dynamicImport("glob")`, not inlined. - `getAllFilesInPath` unit suite (17 tests: ignore patterns, dotfiles/dotdirs, recursive dirs, deterministic sort, `.` pattern) green against the real glob 13.0.6 on Node, **Bun 1.3.14, and Deno 2.8.1**. - Full `unit` + `connectionConfig` projects: 401 passed / 30 skipped against prod. - Full `template` project: 133 passed / 3 skipped, including real end-to-end template builds that exercise `COPY` (the glob path). - Packed the tarball and installed it into a scratch project to confirm the warning is actually gone (output above), plus CJS `require('e2b')` and ESM `import 'e2b'` both load. ## Not fixed here `@e2b/cli` installs still warn, via `@npmcli/package-json@5.2.1 → glob@10.5.0`. Clearing that needs `@npmcli/package-json@7`, whose `engines` (`^20.17.0 || >=22.9.0`) are narrower than the CLI's own (`>=20.18.1 <21 || >=22`, so Node 22.0–22.8 would drop out) — separate change, separate decision. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b5119539ca |
fix(js-sdk): share lazy fetcher loading and drop the new Function import trick (#1607)
Extracts the duplicated api/envd fetcher-loading logic into shared
`createRuntimeFetch` + `buildDispatchedFetch` helpers in `undici.ts`,
fixing two behaviors along the way: a failed fetcher build is no longer
cached forever (the next request retries, with a guarded
compare-and-clear so a stale awaiter can't clobber a newer in-flight
build — covered by a regression test reproducing the microtask
interleaving), and the no-undici fallback now late-binds
`globalThis.fetch` so fetch replacements installed after the first
request (msw, instrumentation) are picked up.
`loadUndici` now uses the shared `dynamicImport` helper, whose import is
kept opaque to downstream bundlers via `webpackIgnore`/`@vite-ignore`
annotations instead of the `new Function('return import(...)')` trick —
so environments that disallow code generation from strings (CSP,
`--disallow-code-generation-from-strings`) now load undici normally
instead of silently degrading to the global fetch. The now-internal
`toUndiciRequestInput`/`UndiciRequestInit` are no longer exported. No
user-facing API changes; verified with the unit suites (lint/typecheck
clean) plus real API integration tests through the new dispatcher path.
### Test-suite fallout from the import fix
Dropping the `new Function` trick exposed a hidden test dependency: that
trick throws under vitest's vm evaluation, so every vitest run had
silently fallen back to the msw-patchable global fetch. With module
loading un-broken, the Node test runs dispatched through real undici,
bypassing msw's `globalThis.fetch` patch — mocked requests escaped to
the real API (real 404s in the tags suite, 3-minute hangs in the
abortSignal suites waiting for msw's `request:start`, and 28 real
template builds per run from the stacktrace suite).
Fixed centrally: `tests/globalFetchFallback.setup.ts`, registered via
`setupFiles` for the unit and template projects, mocks
`buildDispatchedFetch` to run the SDK's real undici-unavailable fallback
(late-bound `globalThis.fetch`), so msw suites need no per-file mock and
future msw suites are covered automatically. Suites that inject their
own `loadUndici` (the api/envd transport tests) keep it, so the
dispatcher wiring itself stays covered. Verified under Node, Bun, and
Cloudflare workerd.
Closes SDK-290
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
59c6996a1e | [skip ci] Release new versions | ||
|
|
4fcf7cb150 |
feat: sync API specs from infra and belt with Copybara (#1564)
The specs in `spec/` were copied from their source repos by hand and had
drifted ~2,400 lines behind infra, so they are now imported with
Copybara (`copy.bara.sky`, run in a pinned Docker image by
`scripts/fetch-spec.sh`): `make codegen` re-fetches them at the commits
pinned in `spec/infra-ref` and `spec/belt-ref` before generating, and
the generated-files CI check fails if the tracked copies don't match the
pins. Regenerating from the current pins picks up the accumulated spec
changes in the generated JS/Python clients (renamed request schemas,
`SandboxNetworkConfig`, `SandboxIam` workload identity,
`FILE_TYPE_SYMLINK`, access-token auth deprecation, volume path-metadata
tweaks). The one handwritten SDK change follows from that: the public
`FileType` enums gain a `SYMLINK` member (JS and both Python surfaces)
so entries envd reports as symlinks show up in `files.list()` and
`getInfo()`/`get_info()` instead of being silently skipped as unknown
types. The custom `spec/remove_extra_tags.py` tag-filtering script is
replaced by Redocly CLI's `filter-in` decorator (`redocly.yaml`), which
produces identical generated JS output; a `filter-out` decorator
additionally drops any operation or component schema the upstream specs
mark `x-not-implemented: true` (currently the SOCKS5
`SandboxEgressProxyConfig`/`egressProxy` surface, which infra flagged as
spec-only); each SDK's bundle now goes to its own gitignored
`spec/openapi_generated.<api>.yml` instead of both pipelines overwriting
one shared file; Python client models now list fields in spec order
instead of alphabetical (mechanical reordering only — construct models
with keyword args). Spec fetches try whatever GitHub token is available
and fall back to the tracked copies with a warning (the public infra
specs also fetch anonymously); in CI a short-lived belt-scoped token is
minted from the org-wide Autofixer GitHub App (no new secrets), so fork
PRs simply fall back for the belt spec; the CI workflows also cache the
Copybara image alongside the codegen image, and the previously ignored
`CODEGEN_IMAGE` env is honored by the Makefile.
## Usage
```sh
# update the specs: bump a pin, then regenerate
echo <infra-commit-sha> > spec/infra-ref
make codegen
# fetch a single spec without regenerating
pnpm fetch:api-spec # spec/openapi.yml from infra
pnpm fetch:envd-spec # spec/envd/ from infra
pnpm fetch:volume-spec # spec/openapi-volumecontent.yml from belt
# try the latest spec without touching the pin
E2B_INFRA_REF=main pnpm fetch:api-spec
# change which endpoint tags an SDK exposes
$EDITOR redocly.yaml && make codegen
```
```ts
// symlinks are now visible in the filesystem API (JS; same shape in Python)
const entries = await sandbox.files.list('/home/user')
const link = entries.find((e) => e.type === FileType.SYMLINK)
console.log(link?.symlinkTarget)
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@e2b/cli@2.15.1
@e2b/python-sdk@2.35.0
e2b@2.36.0
|
||
|
|
ada1744cf1 |
test(js-sdk): run the template test suite on Bun (#1600)
## Description Adds `--project template` to `test:bun` so the Bun CI leg runs the template suite, matching the Deno leg (#1595). No code changes are needed: the template suite previously failed under Bun because Bun's JavaScriptCore elides tail-call frames and the fixed-depth stack walk attributed build errors one frame past the user's call site (the workaround attempt in #1596 was closed in favor of #1599). With #1599's boundary-based frame selection (now merged), the suite passes under Bun as-is. The CI workflow already passes `E2B_API_KEY`/`E2B_DOMAIN` to the Bun leg, and the matrix comment (updated in #1595) already covers Bun re-running API-backed suites, so `package.json` is the only change. ## Testing Full `test:bun` (unit + connectionConfig + template) green locally on Bun 1.3.14 against the real API: 530 passed, 35 skipped, 0 failed — including all 34 stack-trace/caller-directory tests that pin exact user call-site line/columns, the frames Bun used to elide. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ae3f92090 |
feat(js-sdk): run the full unit test suite in Cloudflare workerd (#1593)
## What Promotes `test:cf` from a single dist smoke test to the **full unit + connectionConfig suite running inside Cloudflare's workerd** (`@cloudflare/vitest-pool-workers`) — the same coverage `test:bun` and `test:deno` get. Locally: **74 files / 393 tests green** against prod sandboxes. The real-deploy suite (`test:cf:deploy`) is unchanged and keeps covering the built bundle on actual Cloudflare infrastructure (the pool can't reproduce bundling bugs like #1579). ## SDK fixes the suite surfaced 1. **Dropped-connection mapping for Workers** (`src/envd/rpc.ts`): workerd surfaces a sandbox connection drop as `Network connection lost`, which fell through to a cryptic `SandboxError`. It's now matched like the Node/Bun/Deno variants, so killing a sandbox mid-request surfaces as the health-checked `TimeoutError`: ```ts const cmd = await sandbox.commands.run('sleep 60', { background: true }) await sandbox.kill() await cmd.wait() // now rejects with TimeoutError('…sandbox was killed or reached its end of life…') on Workers too ``` 2. **Double connection release on stream cancel** (`src/connectionConfig.ts`): `wrapStreamWithConnectionCleanup` claimed its `release` was idempotent but had no guard — cancelling a streamed download while a read was in flight ran `cleanup()` twice (both the `cancel` callback and the pending `pull` resolving `done` fire). workerd's stream scheduling hits this deterministically; the pooled connection was double-released. Both are runtime-behavior fixes specific to the JS fetch/streams stack — no Python SDK equivalent applies. ## Test adjustments - **boot_id reads** in the two "filesystem-only pause" tests now use `commands.run('cat …')` instead of `files.read`: envd's non-gzip download path serves procfs files as an empty 200 (filed as e2b-dev/infra#3363 — Go `ServeContent` sizes them by stat, which is 0). Only clients that don't negotiate gzip (workerd's fetch) observe it; the command path sidesteps the bug while keeping the reboot assertion on all runtimes. - **runtime.test.ts** Node-host detection scenarios skip under workerd via the existing host guard (same treatment as Bun/Deno). - **Pool config filters expected unhandled-rejection shapes** via vitest's `onUnhandledError` (not the blanket `dangerouslyIgnoreUnhandledErrors`): workerd reports a rejection as unhandled unless a handler attaches within the same microtask drain — even inline `await expect(op()).rejects` trips it — and vitest never processes the `rejectionhandled` retraction on any runtime, so the suite's deliberate rejections false-positive ~60× per run. A diagnostic pairing `unhandledrejection` with `rejectionhandled` confirmed all of them are handled-late false positives (zero genuine leaks). The filter drops only the shapes the tests provoke (SDK error classes, `ConnectError`, `AbortError`, workerd's `Network connection lost.`, one test stub); unknown rejection shapes and uncaught exceptions still fail the run — verified with a planted never-handled `TypeError` (exit 1). ## CI Rebased onto #1588's per-runtime matrix: the `cloudflare` leg (already ubuntu-only there) now runs the full suite; no extra jobs added. The stale `tests/integration` exclude was dropped after #1591 removed that suite. ## Notes - Suite config needs `nodejs_compat_populate_process_env` + `E2B_API_KEY`/`E2B_DOMAIN` miniflare bindings so the SDK and tests read env like on Node. - The deleted `tests/runtimes/cloudflare/run.test.ts` (dist smoke) is fully subsumed: lifecycle coverage by the suite, bundle coverage by `test:cf:deploy` + `tests/bundle/edgeCompat.test.ts`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3f46d56026 |
fix(sdk): select stack-trace frames by SDK boundary instead of fixed depth (#1599)
## Description Template build stack traces were captured by walking a fixed number of frames (`STACK_TRACE_DEPTH` plus `±1` arithmetic at ~15 call sites), which broke whenever the frame count between `new Error()` and user code shifted — TS class-field initializer frames (#1539) and Bun's tail-call frame elision were both this bug. This PR makes two related changes: 1. **Boundary-based frame selection.** The caller's frame is now the first one whose file lies outside the SDK package, making extra transpiler frames and elided delegating frames irrelevant. In the JS SDK, frame parsing is delegated to `error-stack-parser-es` (ESM-only, so it's a devDependency inlined into both dist formats via tsdown `noExternal` — the engines range includes Node versions without `require(esm)`); the Python SDK equivalently walks `f_back` until `co_filename` leaves the `e2b` package root, in the shared builder used by both sync and async. If no user frame is identifiable (e.g. the SDK is bundled into the caller's own file), capture degrades to no trace rather than a wrong frame. 2. **Dead machinery removed.** Because boundary capture resolves through SDK-internal delegation (`remove()` → `runCmd()`, `fromDockerfile()` → parser) to the user's call site on its own, the suppress/override collection machinery (`runInNewStackTraceContext`, `runInStackTraceOverrideContext`, the enabled/override flags, and their Python equivalents) became redundant and is removed — superseding the approach in #1596. Error `.stack` synthesis (keeping the `Name: message` header and the throw site on `cause`) was prototyped here and backed out — it will come as a follow-up PR. ## Usage No API changes — build errors now point at the user's call site regardless of runtime or transpiler: ```ts const template = Template() .fromBaseImage() .runCmd('./does-not-exist') // ← build failures point exactly here await Template.build(template, 'my-template') ``` ## Testing - JS: `unit` + `template` vitest projects green against the real API (incl. 27 per-method stacktrace tests pinning exact call-site line/columns, `bunInstall` now covered); edge-compat bundle test and CLI build verified; built CJS/ESM dists smoke-tested with `require()`/`import()`. - Python: all 184 template tests green (shared + sync + async, incl. both `test_stacktrace.py` suites, `bun_install` now covered); `ruff` and `ty` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00253c39cc |
feat(python-sdk): migrate envd RPC to the official connectrpc client (#1558)
Replaces the vendored `e2b_connect` client and the custom Go `protoc-gen-connect-python` plugin with the official Connect RPC client for Python ([`connectrpc`](https://github.com/connectrpc/connect-py), transport: `pyqwest`/Rust hyper), and switches the envd messages from Google's `protobuf` runtime to Buf's [`protobuf-py`](https://github.com/bufbuild/protobuf-py) (which `connectrpc` already requires) — the SDK no longer depends on the conflict-prone `protobuf` package at all, and the protoc binary drops out of the codegen image. The wire format (same protos, same JSON) is unchanged. Closing a command or watch stream early now sends `RST_STREAM`, fixing abandoned streams leaking on the shared HTTP/2 connection, and peer resets surface as typed `ConnectError`s. The plumbing mirrors the `e2b.api` layout: shared pieces (a JSON codec that ignores unknown response fields, proxy narrowing, pool tuning) live in `e2b/envd/client_shared.py`, the flavor-specific pyqwest transports (wrapped in pyqwest's retry middleware, see the retry note below) and `create_rpc_client` factories in `e2b/envd/client_sync/` and `e2b/envd/client_async/`, and the default-header/logging interceptors in `e2b/envd/interceptors.py`; `e2b/envd/rpc.py` maps `connectrpc` error codes onto the existing SDK exceptions, so the public API is unchanged (`sandbox.commands.run(...)`, `files.watch_dir(...)`, etc. work exactly as before). The REST API and file upload/download keep using `httpx`. The `proxy` connection option now applies to sandbox RPC calls too — [pyqwest 0.7.0](https://github.com/curioswitch/pyqwest/releases/tag/v0.7.0) added an httpx-style `proxy` parameter to its transports, so commands, PTY, and filesystem watch traffic follow the same proxy as the REST API and file transfers (an earlier revision of this PR could only fall back to `http_proxy`/`https_proxy` env vars for RPC): ```python sandbox = Sandbox.create(proxy="http://user:pass@localhost:8030") # REST *and* RPC (commands, PTY, watch) traffic goes through the proxy result = sandbox.commands.run("echo through-the-proxy") ``` Notes: - `e2b_connect` is no longer shipped in the wheel; code importing it directly should switch to `connectrpc` (`ConnectError`, `Code`) — SDK exception types are unchanged. - The generated `e2b.envd.*.*_pb2` modules are replaced by `protobuf-py` equivalents (`process_pb`, `filesystem_pb`) with a different message API (`Oneof` objects, `has_field`); these are internal modules — `e2b-code-interpreter` and `e2b-desktop` were verified not to import them. - RPC transports are cached per proxy URL. `httpx.URL` and `httpx.Proxy` proxies keep working for RPC calls when they reduce to a proxy URL (`httpx.Proxy` auth is folded back into the URL userinfo); `httpx.Proxy` extras that pyqwest can't express — custom headers, an `ssl_context` — raise `InvalidArgumentException` rather than being silently dropped. - Plain (non-Connect-encoded) HTTP error responses — an edge proxy or gateway answering for envd — keep the vendored client's status mapping even when they carry a JSON body that isn't a valid Connect error (e.g. a gateway's `{"code": 429}` raises `RateLimitException`, not a misleading sandbox-timeout); only JSON bodies with a valid Connect `code` string are left to connectrpc to parse. An envd response that fails to decode surfaces as a `SandboxException` with a clear message — the SDK's JSON codec raises a typed `ConnectError(INTERNAL)` at the source (connectrpc re-raises codec-raised `ConnectError`s unchanged), rather than the error being reconstructed from `__cause__` heuristics in the exception mapper. - pyqwest 0.7.0 explicit transports default to an **empty TLS root store** (0.6.2 used reqwest's defaults), so the envd transports pass `tls_include_system_certs=True`; the dependency floor is `pyqwest>=0.7.0` accordingly. - Connection retries (`E2B_CONNECTION_RETRIES`, default 3) use pyqwest's transport-level retry middleware (`pyqwest.middleware.retry`), narrowed to retry only the builtin `ConnectionError` — raised solely while establishing the connection, before the request could have reached envd — with exponential backoff. A retry can therefore never replay a delivered request, for unary and streaming RPCs alike; the previous stack's replay of unary calls whose connection dropped mid-request is dropped deliberately, since it could re-execute a delivered call (e.g. `SendInput`). Pinned by unit tests plus end-to-end tests driving the generated stubs through the middleware (`tests/test_envd_retry_transport.py`). - For async streaming calls (`commands.run`/`connect`, PTY, `watch_dir`), `request_timeout` bounds opening the stream — the wait until envd confirms with a start event, matching the JS SDK's `requestTimeoutMs` — raising `TimeoutException` and cancelling the HTTP/2 stream when exceeded (pinned frame-level in `tests/test_envd_stream_reset.py`). The running stream stays bounded by the command/watch `timeout`. The sync SDK cannot interrupt its blocking wait, so `request_timeout` is not applied to sync stream setup — both setup and the running stream are bounded by `timeout` (unlimited when `0`). - The RPC logging interceptor was upstreamed to pyqwest as a logging middleware ([curioswitch/pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192)); the SDK keeps its own `LoggingInterceptor` until that merges and ships in a release the SDK can depend on. - `pyqwest` ships binary wheels for manylinux/musllinux (x86_64, aarch64), macOS arm64 + x86_64 (Intel wheels landed in 0.7.0), Windows x64, and PyPy. - The `RST_STREAM`-on-early-close behavior is pinned by frame-level regression tests (`tests/test_envd_stream_reset.py`): a plaintext HTTP/2 server records the frames the real generated clients (with the SDK's codec and interceptors) send — early close via `disconnect()`, close through the logging interceptor, and abandoning the stream must all send `RST_STREAM(CANCEL)`; normal completion must send none (sync + async). - `E2B_MAX_CONNECTIONS` no longer applies to sandbox RPC traffic: reqwest's pool 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. - The sync sandbox modules build one RPC client each and share it across threads — the connectrpc sync client is stateless per call over the process-global transport (verified with a 16-thread frame-level test); only the httpx envd API clients stay per-thread with their transports. - Also fixes numeric env-var parsing (`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`, `E2B_MAX_CONNECTIONS`, `E2B_CONNECTION_RETRIES`): an empty-string value now falls back to the default instead of raising `ValueError` at import time. |
||
|
|
5e141a765f |
fix(js-sdk): use commands.run in Sandbox.getHost() example (#1531) (#1550)
The JSDoc `@example` on the public `Sandbox.getHost()` method calls
`sandbox.commands.exec(...)`, but the `Commands` class has no `exec`
method. It
exposes `run`. Copy-pasting the documented snippet therefore throws:
```
TypeError: sandbox.commands.exec is not a function
```
### Where
`packages/js-sdk/src/sandbox/index.ts`, in the `getHost()` doc comment:
```ts
/**
* ...
* @example
* ```ts
* const sandbox = await Sandbox.create()
* // Start an HTTP server
* await sandbox.commands.exec('python3 -m http.server 3000') // <- no such method
* // Get the hostname of the HTTP server
* const serverURL = sandbox.getHost(3000)
* ```
*/
```
The `Commands` class (`packages/js-sdk/src/sandbox/commands/index.ts`)
exposes
`list`, `sendStdin`, `closeStdin`, `kill`, `connect`, and `run` (four
`run`
overloads), plus a private `start`. There is no `exec`. The correct
method here
is `run`, which is what every other example already uses, including the
sibling
`@example` in this same file (the `commands.run(...)` snippet a few
methods up)
and both Python SDK mirrors (`get_host` in `sandbox_sync/main.py` and
`sandbox_async/main.py` already use `commands.run`).
### Fix
One token, `exec` -> `run`:
```ts
- await sandbox.commands.exec('python3 -m http.server 3000')
+ await sandbox.commands.run('python3 -m http.server 3000')
```
Documentation only. No behavior or type change.
### Parity with the Python SDK
The repo guidelines ask that SDK changes be mirrored across the JS and
Python
SDKs. Here the Python `get_host` examples already use `commands.run`
correctly,
so this defect exists only in the JS SDK doc comment and no Python
change is
needed to reach parity.
### Tests
This is a JSDoc `@example` correction with no runtime code path to
exercise, so
it adds no test, matching the repo's existing precedent for
documentation-only
fixes (e.g. `.changeset/sandbox-list-docstring.md`, and merged doc-fix
PRs such
as #1511 / #1500 / #1260, none of which added a regression test).
Correctness is
that the example now names the real public API: after the change,
`commands.exec`
no longer appears anywhere in the SDK source, and `commands.run` matches
the
`Commands` class and the sibling examples.
Offline gates run locally (Node 20, pnpm 9.15.5):
```
pnpm --filter e2b run lint # oxlint, clean
pnpm --filter e2b run typecheck # tsc --noEmit, clean
pnpm --filter e2b run build # tsc + tsup, ESM/CJS/DTS built
prettier --check src/sandbox/index.ts # clean
```
A changeset (`e2b`, patch) is included.
---
## Linked issues
- None. There is no existing GitHub issue for this; it is a self-evident
public
doc-example defect (the documented snippet throws at runtime). Not
filing a
separate issue for a one-token doc fix.
## Pre-flight checklist (repo AGENTS.md / CLAUDE.md gates)
- [x] `pnpm run format` - `prettier --check` clean on the changed file
- [x] `pnpm run lint` - oxlint clean (exit 0)
- [x] `pnpm run typecheck` - tsc --noEmit clean (exit 0)
- [x] `pnpm run build` - tsc + tsup clean
- [x] Changeset generated - `.changeset/fix-gethost-example-command.md`
(`e2b`: patch)
- [x] Conventional Commit message (`fix(js-sdk): ...`, reuses `js-sdk`
scope)
- [ ] Test added - not applicable (doc-only `@example`; see Tests
section for precedent)
- [ ] DCO / CLA - no sign-off required by this repo; CLA is signed via
`@cla-bot`
on the PR after opening (as on prior PRs #1518 / #1519 / #1507)
Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
Co-authored-by: Anas Khan <anxkhn28@gmail.com>
|
||
|
|
761ee5ebf9 |
docs: instruct linking Linear issues when opening PRs (#1604)
## Description Adds a rule to `CLAUDE.md` instructing agents to use the Linear MCP (if available) when opening a new pull request — either linking to related existing issues or creating a new issue from the PR description. Linked issue: [SDK-267](https://linear.app/e2b/issue/SDK-267/claudemd-require-linear-issue-linking-when-opening-prs) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ee4414e6d |
feat(js-sdk): run the template test suite on Deno (#1595)
## What Extends the Deno vitest run (#1585) with the `template` project and fixes the real runtime bug the suite surfaced. Split out of #1594 (Bun counterpart: #1596). ```jsonc // packages/js-sdk/package.json "test:deno": "deno run -A npm:vitest run --project unit --project connectionConfig --project template", ``` ## Bug — Deno: template uploads used chunked transfer encoding Deno's native `fetch` ignores an explicit `Content-Length` header on stream bodies and falls back to `Transfer-Encoding: chunked` — exactly the failure #1243 fixed for Node, since S3-compatible presigned PUT URLs reject chunked uploads with 501. `uploadFile` now streams the spooled archive through **undici's `fetch`** (via the existing `loadUndici()` helper — undici 8 where it imports, undici 7 on Bun, global `fetch` where undici isn't resolvable, e.g. bundled apps), which honors the `Content-Length` header on stream bodies on every runtime. One upload path, no runtime sniffing. Approaches rejected along the way, all verified empirically with 1GB uploads + RSS sampling: - **File-backed `Blob` body (`fs.openAsBlob`)** — lazy on Node/Bun, but Deno's shim reads the whole file into memory eagerly (denoland/deno#32316), and Bun infers an unstrippable MIME type from the extension whose `Content-Type` breaks presigned signatures (403 against production storage). - **`node:http(s)` on Deno** — works (and is memory-bounded), but can't be unified: Bun's `node:http` ignores abort signals, and it's a second code path. Known caveat: Deno's `Readable.toWeb` shim has no backpressure, so the archive is buffered in memory during upload on Deno (Node and Bun stream in lockstep with the socket). Filed upstream as denoland/deno#36275 — accepted as Deno's to fix rather than worked around here. As part of this, `tarFileStream` became `spoolTarArchive`, returning `{ path, size, cleanup }` with caller-owned cleanup instead of a self-deleting read stream. `tests/template/uploadFile.test.ts` also asserts no `Content-Type` header is sent. ## Python SDK parity Intentionally none: `upload_file` already sends a sized file body via httpx. ## Testing - Real template builds (`tests/template/build.test.ts`, against prod S3 presigned URLs) green under **Node, Deno, and Bun** - `uploadFile` + `spoolTarArchive` suites green under Node, Deno, and Bun - `tests/template/abortSignal.test.ts` green under Deno - `pnpm build`, `lint`, `typecheck`, `prettier --check` clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5417dd4f9f |
fix(deps): resolve all open Dependabot alerts (#1598)
## Summary Fixes all 8 open [Dependabot alerts](https://github.com/e2b-dev/E2B/security/dependabot), all in `pnpm-lock.yaml`: | Package | Severity | Alerts | Before | After | How | |---|---|---|---|---|---| | `@vitest/browser` | critical | #328 | 4.1.8 | 4.1.10 | updated the vitest family in js-sdk and cli devDeps (4.1.10 peer-requires `vitest@4.1.10` exactly) | | `tar` | critical/high/medium ×4 | #324–#327 | 7.5.16 | 7.5.21 | bumped the js-sdk runtime dep floor to `^7.5.19` + repo-wide override | | `sharp` | high | #329 | 0.34.5 | 0.35.3 | new override (pinned exactly by miniflare, dev-only) | | `shell-quote` | high | #323 | 1.8.4 | 1.10.0 | widened existing override (dev-only, via npm-run-all) | | `brace-expansion` | high | #322 | 2.1.0 | 2.1.2 | widened existing override | The only runtime-dependency change is `tar` in the js-sdk (used for template build contexts), so a patch changeset for `e2b` is included. The CLI bundles the SDK and its dependencies into `dist/index.js`, so the published CLI also ships the vulnerable `tar` — a patch changeset for `@e2b/cli` is included to rebundle it. Everything else is dev tooling or lockfile-only. ## Verification - `pnpm run lint` and `pnpm run typecheck` pass (the 7 python-sdk ty diagnostics pre-exist on main) - js-sdk: unit + connectionConfig (393 passed) and template projects (132 passed, exercises the new `tar` end-to-end against the real API) on vitest 4.1.10; `pnpm run build` clean - js-sdk `test:cf` passes — miniflare/workerd boots with sharp 0.35.3 - cli: full suite green (103 passed) on vitest 4.1.10 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
67bf112efc |
test(js-sdk): rename deprecated test.scoped() to test.override() (#1597)
vitest 4.1 deprecates `test.scoped()` in favor of `test.override()`, emitting 15 warnings during test collection in CI. This renames all `sandboxTest.scoped()` fixture overrides to `sandboxTest.override()` across the six affected test files (network, snapshot, internetAccess, secure, files/signing, commands/envVars). It's a pure rename — the vitest 4.1.8 types confirm an identical signature — and `vitest list` on all six files now collects with zero deprecation warnings. Test-only change, so no changeset. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e00503b090 |
fix(ci): recover release 30006966441 and retry lockfile update with backoff (#1589)
## What happened
Release run
[30006966441](https://github.com/e2b-dev/E2B/actions/runs/30006966441)
successfully published **e2b@2.35.3** and **@e2b/cli@2.15.0** to npm and
pushed both tags, but then failed on the **Update lock file** step:
`pnpm i` ran ~6 seconds after `npm publish` and the registry had not
propagated the new version yet (`ERR_PNPM_NO_MATCHING_VERSION: No
matching version found for e2b@^2.35.3 — the latest release of e2b is
"2.35.2"`). Because that step failed, the **Commit new versions** step
was skipped, leaving main with stale versions and unconsumed changesets.
Auditing the rest of the publish path for similar races also turned up a
long-dead step: the `@e2b/sdk` alias republish.
## Changes
**Commit 1 — replay the missing release commit.** Reproduces exactly
what the bot would have committed: `pnpm run version` (consumes the
three changesets, bumps js-sdk 2.35.2 → 2.35.3 and cli 2.14.0 → 2.15.0)
followed by `pnpm i --no-link --no-frozen-lockfile` (now succeeds — the
registry has long since propagated). The only commit that landed on main
after the release was dispatched
([
|
||
|
|
aa3c2593b9 |
test(js-sdk): remove unused integration test suite (#1591)
## Summary Removes `packages/js-sdk/tests/integration/` — the suite was never wired into CI: no workflow references `test:integration` or sets the `E2B_INTEGRATION_TEST` env var that gated every test. The tests were also stale, referencing hardcoded template IDs (`en716jw99aj63v1k8ugh`, `integration-test-v1`) that likely no longer exist and passing `timeoutMs: 120` (120 ms). Also removes the `test:integration` script, the `integration` vitest project, and the unused `isIntegrationTest` helper from `tests/setup.ts`. Test-only change, no changeset needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e334c87f8f |
ci(js-sdk): split test workflow into parallel per-runtime jobs (#1588)
Splits the JS SDK test workflow's serial ubuntu job (Node → Cloudflare pool → Cloudflare deploy → Bun → Deno) into a `fail-fast: false` matrix of parallel legs: `node` on ubuntu and windows, plus `bun`, `deno`, `cloudflare`, and `cloudflare-deploy` on ubuntu. This cuts wall-clock time to the slowest single suite and lets a failed runtime be identified and re-run individually; Playwright setup is gated to the `node` legs (the only ones running the vitest browser project), while every leg keeps `pnpm build` since the unit bundle test and both Cloudflare configs require `dist/` in CI. A new `node-only` workflow input collapses the matrix to the two Node legs, and the staging caller in `sdk_tests.yml` sets it — Bun/Deno only run API-free unit suites and the Cloudflare legs just add sandbox load, so the extra runtimes are exercised against production only. The `workflow_call` interface stays backward-compatible, so `release.yml`, `release-candidate.yml`, and the required `SDK Tests / SDK Tests Status` check need no changes and keep the full matrix. Production coverage is identical to before — the Windows job never ran the extra suites anyway. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2defe39bd7 |
feat(cli): rename team to project in ~/.e2b/config.json (#1570)
Bumps `~/.e2b/config.json` to `version: 2` and renames
`teamName`/`teamId`/`teamApiKey` to
`projectName`/`projectId`/`projectApiKey`. The rename is internal to the
config file format — all user-facing CLI output, flags (`--team`), and
env vars (`E2B_TEAM_ID`) still say "team", and API `teamID` parameters
are unchanged.
Existing v1 configs keep working: they are converted to the new format
in memory on read, and the file on disk is left untouched — the v2
format is only persisted through paths that write the config anyway
(login, `e2b auth configure`, token refresh), so older CLI versions can
still read the file in the meantime. Unrecognized configs are no longer
deleted either; the CLI treats them as signed out and `e2b auth login`
overwrites them. Tools that read the config file directly must handle
the new field names once the file is written in the v2 format.
## Usage
```jsonc
// ~/.e2b/config.json (fresh login, or any config write after upgrading)
{
"version": 2,
"projectName": "default",
"projectId": "team-id",
"projectApiKey": "e2b_...",
// identity, oauth, tokens, last_refresh unchanged
}
```
CLI output is unchanged:
```bash
$ e2b auth info
You are logged in as user@example.com,
Selected team: default (team-id)
```
## Testing
`user_config_migration.test.ts` covers in-memory v1→v2 migration, v2
pass-through, and unrecognized configs being treated as signed out
without deleting the file; existing config-permissions and backend
integration tests updated to the new fields. `format`, `lint`,
`typecheck`, `build`, and `pnpm run test` pass (backend integration
suites are environment-gated on credentials).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
e2b@2.35.3
@e2b/cli@2.15.0
|
||
|
|
e29d406887 |
feat(js-sdk): run the vitest unit suite on Deno (#1585)
## Description `pnpm test:deno` now runs the full vitest suite — the `unit` and `connectionConfig` projects, 421 sandbox/files/commands/pty/git/api/config tests — under the Deno runtime via `deno run -A npm:vitest run --project unit --project connectionConfig`, replacing the previous single dist-based smoke test (superseded — the suite covers the SDK under Deno far more thoroughly). The CI step runs on ubuntu only and covers the same projects as the Bun suite step from #1584, and the Deno pin is bumped from 1.46.3 to 2.8.1 (`setup-deno@v2`) since vitest needs Deno 2's Node compat. Also drops the `edge` vitest project: `tests/runtimes/edge/` no longer exists, so it matched zero files. Rebased on main after #1584: the off-Node fetch-caching fix originally in this PR was superseded by #1584's late-binding fix, which also makes the whole suite (including the per-proxy cache tests) pass under Deno with no test changes — so this PR is pure test/CI wiring. Verified locally on Deno 2.8.1: unit project green (349 passed, 0 failed, 29 skipped — same skips as Node), connectionConfig project green (43 passed), and Node suite green. ## Usage ```bash cd packages/js-sdk pnpm test:deno ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |