## Summary
When `lifecycle.on_timeout` is set it wins; otherwise we fall back to
the `auto_pause` argument.
Previously the Python SDKs subscripted `lifecycle["on_timeout"]`, which
raised `KeyError` if a caller passed a `lifecycle` dict missing that key
(TypedDict is not enforced at runtime). The JS SDK silently used the
whole `lifecycle` object even when `onTimeout` was undefined. In both
cases, mixing `lifecycle` and `auto_pause` had inconsistent and
surprising behavior across the public surfaces (`create` vs
`beta_create`).
Now both SDKs use `.get`/optional chaining on `on_timeout` and only
treat `lifecycle` as authoritative when that field is actually present.
Touched files:
- `packages/python-sdk/e2b/sandbox_async/sandbox_api.py`
- `packages/python-sdk/e2b/sandbox_sync/sandbox_api.py`
- `packages/js-sdk/src/sandbox/sandboxApi.ts`
---------
Co-authored-by: Jakub Novak <jakub@e2b.dev>
## Summary
- Add optional `name` parameter to `createSnapshot` / `create_snapshot`
in the JS and Python SDKs so callers can name the resulting snapshot
template.
- Return the `names` field from the snapshot API on `SnapshotInfo` (both
in `createSnapshot` responses and in `listSnapshots` paginator results)
so callers can discover the namespaced snapshot names.
- Includes a changeset (`patch` for `e2b` and `@e2b/python-sdk`).
## Test plan
- [ ] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` all pass
locally
- [ ] Integration tests on a sandbox with valid credentials:
`sandbox.createSnapshot({ name: 'my-snap' })` returns non-empty `names`
Resolves https://github.com/e2b-dev/E2B/issues/1249
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
- Replace the sequential `for sandbox in sandboxes: await
sandbox.kill()` teardown in `async_sandbox_factory` with
`asyncio.gather(..., return_exceptions=True)` so a failure on one kill
no longer blocks cleanup of the rest, and teardown runs concurrently.
- Hoist the `_test_failed` check above the loop so the failure log is
printed once per fixture rather than per sandbox.
## Test plan
- [ ] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck`
(python-sdk) all pass
- [ ] `pnpm run test` against affected python async sandbox tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Enables HTTP/2 for JS SDK sandbox envd traffic in Node by routing envd
RPC/API requests through undici with an HTTP/2-enabled dispatcher.
Non-Node runtimes continue to use global fetch. Management API and
volume clients are unchanged.
Requires bumping node from >=20 to >= 20.18.1 for undici
## Summary
Adds a patch changeset for the `e2b` JS SDK to cover #1306 (commit
bd99b23c1), which removed the unused `npm-check-updates` devDependency
to clear the remaining `tar@6` Dependabot security alerts.
The original PR landed without a changeset, so the next release would
skip publishing the SDK despite the `package.json` change. This file
ensures the dependency cleanup gets a proper patch bump.
## Test plan
- [x] `.changeset/drop-npm-check-updates.md` follows the repo's existing
changeset format (frontmatter + summary line)
- [ ] Changesets bot picks up the entry on the PR
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Resolves the remaining 6 high-severity Dependabot alerts for `tar` on
the default branch. `tar@6.2.1` was being pulled in transitively via
`npm-check-updates@16 -> pacote@15 / cacache -> tar@^6`, and
Dependabot's `<= 7.5.10` ranges include 6.x semver-wise. Since
`npm-check-updates` was declared as a `devDependency` but never actually
invoked anywhere (no script, CI workflow, or doc references it),
removing it entirely is cleaner than bumping it — alerts cleared with
zero risk of regression.
After removal, the lock contains only `tar@7.5.12`, which satisfies all
six advisories.
## Test plan
- [x] `pnpm run lint` (js-sdk + cli)
- [x] `pnpm run typecheck` (js-sdk + cli)
- [x] `pnpm run format` (js-sdk + cli)
- [x] tar-related unit tests pass (`tests/template/utils`,
`tests/template/uploadFile` — 54 tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Resolves all 31 open [Dependabot
alerts](https://github.com/e2b-dev/e2b/security/dependabot) across the
workspace.
- **npm** — added range-based `pnpm.overrides` to bump vulnerable
transitive deps to their patched versions: postcss, vite, lodash,
brace-expansion, picomatch (2.x + 4.x), yaml, @tootallnate/once,
smol-toml, flatted, and minimatch (3.x/5.x/9.x/10.x).
- **python-sdk** — bumped dev deps in `poetry.lock`: pytest 7.4 → 9.0.3
(with constraint update in `pyproject.toml`), pytest-asyncio 0.23 → 1.3
(required for pytest 9), python-dotenv 1.2.2, pygments 2.20.0, requests
2.33.1, black 26.3.1; removed 4 now-unused `# ty: ignore` directives
that pytest 9's stricter type signatures made obsolete.
## Test plan
- [x] \`pnpm run typecheck\` passes
- [x] \`pnpm run lint\` passes
- [x] \`pnpm run format\` clean
- [x] CLI tests (80/80) and js-sdk/python-sdk unit tests pass;
integration tests not run locally (need \`E2B_API_KEY\`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Consolidates the fix and tests from #1285 and #1293 into a single PR.
- **js-sdk**: `uploadFile` used to pass a Node `Readable` directly to
`fetch`, causing undici to fall back to `Transfer-Encoding: chunked`. S3
presigned PUT URLs reject chunked with 501 NotImplemented. Fix buffers
the archive first so `Content-Length` is set. Includes:
- Regression test that spins up a local HTTP server and asserts
`Content-Length` is set and matches the body, and `Transfer-Encoding` is
not chunked.
- Type-fix for the CLI's typecheck (cast `Pack` →
`AsyncIterable<Buffer>`).
- Dynamic import of `node:stream/consumers` so the browser bundle
doesn't pull it in.
- **python-sdk**: Adds sync + async regression tests for `upload_file`
that guard against the same class of bug (someone swapping
`tar_buffer.getvalue()` for a stream/generator). No Python code change —
the current implementation already passes bytes to `httpx.put(...,
content=...)`.
Authorship of the original JS fix commit preserved (truffle-dev).
Closes#1243.
## Test plan
- [x] `pnpm run test tests/template/uploadFile.test.ts` — passes
- [x] `pnpm run typecheck` / `lint` clean across js-sdk and cli
- [x] `poetry run pytest tests/sync/template_sync/test_upload_file.py
tests/async/template_async/test_upload_file.py -v` — both pass
- [x] `poetry run make format` / `make lint` / `make typecheck` clean
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: truffle <truffleagent@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
- Deletes install test files for apt, bun, npm, and pip in both JS and
Python SDKs
- Removes sync and async variants in Python
- Stacktrace tests for these install methods are kept
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Bumps metrics polling from 15s to 30s in Python async/sync and JS SDK
tests so the backend has enough headroom to populate metrics under load
— this test was the new #1 CI offender (5/9 Python runs and 4/10 JS runs
failed).
- Raises the async Python sandbox timeout from 20s to 60s for parity
with sync, and adds per-test timeout overrides
(`@pytest.mark.timeout(60)` / `{ timeout: 60_000 }`) so polling can
complete under the default 30s pytest/vitest cap.
- Happy path is unchanged: the loop still breaks as soon as metrics
appear.
## Test plan
- [x] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` pass
- [x] `test_sbx_metrics` (Python async) passed locally in 8.4s
- [x] `test_sbx_metrics` (Python sync) passed locally in 15.6s
- [x] `metrics.test.ts` (JS) passed locally in 20.7s
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
Fixes#1154
When creating a sandbox with an `mcp` config, the JSON-serialized config
is interpolated directly into a shell command wrapped in single quotes.
Since `json.dumps()` / `JSON.stringify()` do not escape single quotes,
any MCP config value containing a single quote (e.g., API keys, tokens,
URLs) breaks out of shell quoting and allows arbitrary command execution
inside the sandbox.
## Changes
### Python SDK (`sandbox_async/main.py`, `sandbox_sync/main.py`)
- Use `shlex.quote()` to properly escape the JSON config string (4
locations)
- `shlex.quote()` is a stdlib function designed exactly for this purpose
### JS/TS SDK (`sandbox/index.ts`)
- Add a `shellQuote()` helper that escapes single quotes using the
standard `'\'''` pattern (equivalent to Python's `shlex.quote()`)
- Apply it to both MCP config interpolation sites (2 locations)
## Before / After
**Before** (vulnerable):
```
mcp-gateway --config '{"servers": {"test": {"envs": {"KEY": "it's a value"}}}}'
# ^^ breaks out
```
**After** (safe):
```
mcp-gateway --config '{"servers": {"test": {"envs": {"KEY": "it'\''s a value"}}}}'
# ^^^^ properly escaped
```
## Testing
Verified escaping behavior for both Python (`shlex.quote`) and JS
(`shellQuote`) with the PoC from the issue — single quotes in config
values are properly escaped and no longer allow shell breakout.
---------
Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
- fix typos in hand-written docs and comments
- rename typoed helper variables in the CLI
- fix typoed test identifiers and descriptions in the JS SDK tests
- fix typoed credential warning text in the Python SDK
## Testing
- not run
Closes#1281
## Summary
- Adds `template` as an optional property on `SandboxOpts` in the JS
SDK, enabling `Sandbox.create({ template: 'my-template' })` syntax
- Updates both `create` and `betaCreate` to check `opts.template` before
falling back to the default template
- Python SDK already supports `Sandbox.create(template='template')` via
named parameters, so no changes needed there
## Test plan
- [ ] Verify `Sandbox.create({ template: 'base' })` works
- [ ] Verify `Sandbox.create('base')` still works (backwards compatible)
- [ ] Verify `Sandbox.create()` still defaults to `'base'`
- [ ] Verify MCP template fallback still works when no template is
specified
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Fixes the Python SDK `write_files` docstring (both sync and async)
which incorrectly stated that writing to a non-existing directory would
produce an error
- The backend actually auto-creates parent directories, consistent with
the `write()` docstring and existing tests
(`test_write_to_non_existing_directory`)
## Test plan
- [x] Verified behavior with a test script — both `write()` and
`write_files()` auto-create nested directories
- [x] Existing tests pass (`test_write_to_non_existing_directory`,
`writeFiles creates parent directories`)
## Summary
In the CLI's `sandbox create` command, the `connectSandbox` function's
`finally` block previously called `sandbox.kill()` when the terminal
session ended. This replaces it with `sandbox.setTimeout(1_000)` so the
sandbox expires implicitly after 1 second rather than being explicitly
killed.
The motivation is that an explicit `kill()` can trigger deletion of
historic sandbox snapshots, whereas letting the sandbox time out avoids
that side effect.
### Updates since last revision
Addressed review feedback about a race condition: `clearInterval` stops
future keep-alive ticks but cannot cancel one already in-flight. The
keep-alive callback now stores its promise in a `pendingKeepAlive`
variable, and the `finally` block awaits it (with `.catch(() => {})`)
before setting the 1s shutdown timeout. This ensures an in-flight
`setTimeout(30_000)` cannot silently override the shutdown timeout.
## Review & Testing Checklist for Human
- [ ] **Verify that `sandbox.setTimeout(1_000)` does not trigger
snapshot deletion** — this is the core assumption behind the change.
Confirm that the implicit expiry path in the backend behaves differently
from the explicit `kill()` path with respect to snapshot preservation.
- [ ] **Test `e2b sandbox create` end-to-end**: connect a terminal,
exit, and confirm the sandbox is cleaned up within a few seconds and no
snapshots are lost.
- [ ] **Review the race condition fix**: confirm that `await
pendingKeepAlive.catch(() => {})` correctly serializes against the last
in-flight keep-alive before the 1s timeout is applied. Note that the
interval callback is no longer `async` — it just assigns the promise.
- [ ] **Edge case: what happens if `setTimeout` fails?** The sandbox
would remain alive with its previous 30s keep-alive timeout. Decide if
that's acceptable or if a fallback is needed.
### Notes
- The 1-second timeout value was chosen per the request. Adjust if a
different grace period is preferred.
- The keep-alive interval (`clearInterval`) is still stopped before
awaiting the pending promise, so no new ticks will fire.
Link to Devin session:
https://app.devin.ai/sessions/68081ba06fa54be9b8127ba1d68481ae
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ben@e2b.dev <ben@e2b.dev>
## Summary
- Adds optional `gzip` parameter to sandbox file read/write operations
across JS and Python SDKs
- Uploads are gzip-compressed via `CompressionStream` (JS) /
`gzip.compress` (Python) when enabled, downloads request
`Accept-Encoding: gzip`
- Only applies to the octet-stream upload path (envd >= 0.5.7), so older
envd versions are unaffected
- Includes tests for both SDKs covering write+read with gzip, write gzip
+ read plain, multi-file writes, and byte format reads
## Test plan
- [ ] Run JS SDK content encoding tests (`contentEncoding.test.ts`)
- [ ] Run Python async/sync content encoding tests
(`test_content_encoding.py`)
- [ ] Integration test with envd backend supporting `Content-Encoding:
gzip`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Switches sandbox filesystem file uploads from `multipart/form-data` to
`application/octet-stream` in both the JS and Python SDKs
- Each file is now uploaded as raw binary with the path passed as a
query parameter, matching the `application/octet-stream` content type in
the envd API spec
- Multi-file writes send one request per file sequentially
## Test plan
- [ ] Run JS SDK filesystem write tests (`pnpm run test` in
`packages/js-sdk`)
- [ ] Run Python SDK filesystem write tests (`pytest` in
`packages/python-sdk`)
- [ ] Verify single file write, multi-file write, and various data types
(string, bytes, streams)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Increases the default timeout for volume `writeFile`/`write_file`
operations from 60 seconds to 1 hour in both the JS and Python SDKs.
Other volume operations retain the existing 60s default. Users can still
override via `requestTimeoutMs` (JS) or `request_timeout` (Python).
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
- Made `runCode()` and `run_code()` references in READMEs link to the
[code interpreting docs](https://e2b.dev/docs/code-interpreting)
- Updated root README, js-sdk README, and python-sdk README
## Test plan
- [ ] Verify links render correctly on GitHub
- [ ] Confirm docs URL resolves
## Summary
- Adds a minor changeset for both `e2b` (JS SDK) and `@e2b/python-sdk`
(Python SDK) to publish updated READMEs
## Test plan
- [ ] Verify changeset format is correct
- [ ] Confirm version bump triggers on merge
## Summary
- Updated root README, js-sdk README, and python-sdk README to show base
`e2b` SDK install and usage as the default
- Code-interpreter is now shown as an optional step for when
`runCode()`/`run_code()` is actually needed
- SDK links in descriptions now point to base `e2b` packages on npm/PyPI
## Why
The base `e2b` package covers commands, files, git, networking, and
sandbox lifecycle. Users who don't need code execution shouldn't be
directed to install `@e2b/code-interpreter` / `e2b-code-interpreter` as
their first step.
## Test plan
- [ ] Verify README renders correctly on GitHub
- [ ] Confirm base SDK examples use correct import syntax
- [ ] Confirm code-interpreter section still shows correct usage for
`runCode()`
CLI now supports the `info` command that mirrors the python/js getInfo()
usage:
```
$ e2b sandbox info xxx --format json
{
"sandboxId": "xxx",
"templateId": "xxx",
"name": "stdin",
"metadata": {},
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:32:28.055Z",
"endAt": "2026-03-19T01:37:28.055Z",
"state": "running",
"cpuCount": 2,
"memoryMB": 1024
}
```
```
$ e2b sandbox info xxx
Sandbox info for xxx:
Sandbox ID: xxx
Template ID: xxx
Alias: stdin
State: running
Started at: 3/18/2026, 6:32:28 PM
End at: 3/18/2026, 6:37:28 PM
vCPUs: 2
RAM MiB: 1024
Envd version: xxx
metadata: {}
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: adds a new CLI subcommand and an integration test, with no
changes to existing command behavior or auth flows beyond reusing
`ensureAPIKey`.
>
> **Overview**
> Adds a new `e2b sandbox info <sandboxID>` subcommand (alias `in`) that
calls `Sandbox.getFullInfo`, supports `--format pretty|json`, and strips
`envdAccessToken` from output.
>
> Registers the command under `sandbox` and adds a backend integration
test asserting the JSON output includes the correct `sandboxId` and
`state`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7dbe8b7655e5a7ce023644a5cd7b422f1d1fc974. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
extracts the `allow_internet_access`, `lifecycle`, and `network` configs
to the get info responses from the api when present.
Create a sandbox with lifecycle and network rules, check info while
running, pause it, and check info again. Network rules, lifecycle
config, and `allowInternetAccess` all returned while running and paused
```
$ e2b sandbox info xxx --format json
# running
{
"sandboxId": "xxx",
"templateId": "xxx",
"name": "stdin",
"metadata": {},
"allowInternetAccess": true,
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:39:56.238Z",
"endAt": "2026-03-19T01:44:56.238Z",
"state": "running",
"cpuCount": 2,
"memoryMB": 1024,
"network": {
"allowOut": ["api.example.com", "cdn.example.com"],
"denyOut": ["0.0.0.0/0"],
"allowPublicTraffic": true
},
"lifecycle": {
"onTimeout": "pause",
"autoResume": true
}
}
# paused
{
"sandboxId": "xxx",
"templateId": "xxx",
"metadata": {},
"allowInternetAccess": true,
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:39:56.238Z",
"endAt": "2026-03-19T01:40:27.964Z",
"state": "paused",
"cpuCount": 2,
"memoryMB": 1024,
"network": {
"allowOut": ["api.example.com", "cdn.example.com"],
"denyOut": ["0.0.0.0/0"],
"allowPublicTraffic": true
},
"lifecycle": {
"onTimeout": "pause",
"autoResume": true
}
}
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Updates the public `sandbox info` response shape across OpenAPI, JS,
and Python SDKs, which may impact downstream consumers that assume the
previous schema. Risk is moderate since changes are additive/optional
but touch generated models and response mapping logic.
>
> **Overview**
> **Sandbox info responses now include network and lifecycle
configuration when present.** The OpenAPI spec and generated JS schema
extend `SandboxDetail` with `allowInternetAccess`, `network`, and a new
`lifecycle` object (with `SandboxOnTimeout` and `SandboxLifecycle`).
>
> The JS SDK updates `SandboxApi.getFullInfo()` and exported types to
return these fields, introducing `SandboxInfoLifecycle` for info
responses. The Python SDK updates generated client models accordingly,
adds `SandboxLifecycle`/`SandboxOnTimeout` models, and maps
`SandboxDetail.network`/`SandboxDetail.lifecycle` into `SandboxInfo`
(plus exports `SandboxInfoLifecycle`).
>
> A changeset bumps `@e2b/python-sdk` and `e2b` as minor for the
expanded info payload.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
adb22292c08b1db9c8fe60c702f83fee2695af97. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->